2
2
mirror of https://github.com/octoleo/restic.git synced 2024-11-26 23:06:32 +00:00

Merge pull request #580 from restic/remove-juju-errors

Change errors library
This commit is contained in:
Alexander Neumann 2016-08-30 21:23:53 +02:00
commit 769f06cea2
95 changed files with 544 additions and 2715 deletions

View File

@ -3,7 +3,9 @@
package main package main
import ( import (
"bufio"
"bytes" "bytes"
"errors"
"flag" "flag"
"fmt" "fmt"
"io" "io"
@ -17,6 +19,12 @@ import (
"strings" "strings"
) )
// ForbiddenImports are the packages from the stdlib that should not be used in
// our code.
var ForbiddenImports = map[string]bool{
"errors": true,
}
var runCrossCompile = flag.Bool("cross-compile", true, "run cross compilation tests") var runCrossCompile = flag.Bool("cross-compile", true, "run cross compilation tests")
var minioServer = flag.String("minio", "", "path to the minio server binary") var minioServer = flag.String("minio", "", "path to the minio server binary")
var debug = flag.Bool("debug", false, "output debug messages") var debug = flag.Bool("debug", false, "output debug messages")
@ -345,7 +353,30 @@ func (env *TravisEnvironment) RunTests() error {
return err return err
} }
return runGofmt() if err = runGofmt(); err != nil {
return err
}
deps, err := findImports()
if err != nil {
return err
}
foundForbiddenImports := false
for name, imports := range deps {
for _, pkg := range imports {
if _, ok := ForbiddenImports[pkg]; ok {
fmt.Fprintf(os.Stderr, "========== package %v imports forbidden package %v\n", name, pkg)
foundForbiddenImports = true
}
}
}
if foundForbiddenImports {
return errors.New("CI: forbidden imports found")
}
return nil
} }
// AppveyorEnvironment is the environment on Windows. // AppveyorEnvironment is the environment on Windows.
@ -413,6 +444,46 @@ func updateEnv(env []string, override map[string]string) []string {
return newEnv return newEnv
} }
func findImports() (map[string][]string, error) {
res := make(map[string][]string)
cwd, err := os.Getwd()
if err != nil {
return nil, fmt.Errorf("Getwd() returned error: %v", err)
}
gopath := cwd + ":" + filepath.Join(cwd, "vendor")
cmd := exec.Command("go", "list", "-f", `{{.ImportPath}} {{join .Imports " "}}`, "./src/...")
cmd.Env = updateEnv(os.Environ(), map[string]string{"GOPATH": gopath})
cmd.Stderr = os.Stderr
output, err := cmd.Output()
if err != nil {
return nil, err
}
sc := bufio.NewScanner(bytes.NewReader(output))
for sc.Scan() {
wordScanner := bufio.NewScanner(strings.NewReader(sc.Text()))
wordScanner.Split(bufio.ScanWords)
if !wordScanner.Scan() {
return nil, fmt.Errorf("package name not found in line: %s", output)
}
name := wordScanner.Text()
var deps []string
for wordScanner.Scan() {
deps = append(deps, wordScanner.Text())
}
res[name] = deps
}
return res, nil
}
func runGofmt() error { func runGofmt() error {
dir, err := os.Getwd() dir, err := os.Getwd()
if err != nil { if err != nil {

View File

@ -2,7 +2,6 @@ package main
import ( import (
"bufio" "bufio"
"errors"
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
@ -14,6 +13,8 @@ import (
"strings" "strings"
"time" "time"
"github.com/pkg/errors"
"golang.org/x/crypto/ssh/terminal" "golang.org/x/crypto/ssh/terminal"
) )
@ -223,7 +224,7 @@ func (cmd CmdBackup) newArchiveStdinProgress() *restic.Progress {
func filterExisting(items []string) (result []string, err error) { func filterExisting(items []string) (result []string, err error) {
for _, item := range items { for _, item := range items {
_, err := fs.Lstat(item) _, err := fs.Lstat(item)
if err != nil && os.IsNotExist(err) { if err != nil && os.IsNotExist(errors.Cause(err)) {
continue continue
} }
@ -231,7 +232,7 @@ func filterExisting(items []string) (result []string, err error) {
} }
if len(result) == 0 { if len(result) == 0 {
return nil, errors.New("all target directories/files do not exist") return nil, restic.Fatal("all target directories/files do not exist")
} }
return return
@ -239,7 +240,7 @@ func filterExisting(items []string) (result []string, err error) {
func (cmd CmdBackup) readFromStdin(args []string) error { func (cmd CmdBackup) readFromStdin(args []string) error {
if len(args) != 0 { if len(args) != 0 {
return fmt.Errorf("when reading from stdin, no additional files can be specified") return restic.Fatalf("when reading from stdin, no additional files can be specified")
} }
repo, err := cmd.global.OpenRepository() repo, err := cmd.global.OpenRepository()
@ -273,7 +274,7 @@ func (cmd CmdBackup) Execute(args []string) error {
} }
if len(args) == 0 { if len(args) == 0 {
return fmt.Errorf("wrong number of parameters, Usage: %s", cmd.Usage()) return restic.Fatalf("wrong number of parameters, Usage: %s", cmd.Usage())
} }
target := make([]string, 0, len(args)) target := make([]string, 0, len(args))
@ -311,7 +312,7 @@ func (cmd CmdBackup) Execute(args []string) error {
if !cmd.Force && cmd.Parent != "" { if !cmd.Force && cmd.Parent != "" {
id, err := restic.FindSnapshot(repo, cmd.Parent) id, err := restic.FindSnapshot(repo, cmd.Parent)
if err != nil { if err != nil {
return fmt.Errorf("invalid id %q: %v", cmd.Parent, err) return restic.Fatalf("invalid id %q: %v", cmd.Parent, err)
} }
parentSnapshotID = &id parentSnapshotID = &id

View File

@ -25,10 +25,6 @@ func (cmd CmdCache) Usage() string {
} }
func (cmd CmdCache) Execute(args []string) error { func (cmd CmdCache) Execute(args []string) error {
// if len(args) == 0 || len(args) > 2 {
// return fmt.Errorf("wrong number of parameters, Usage: %s", cmd.Usage())
// }
repo, err := cmd.global.OpenRepository() repo, err := cmd.global.OpenRepository()
if err != nil { if err != nil {
return err return err

View File

@ -2,7 +2,6 @@ package main
import ( import (
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"os" "os"
@ -33,7 +32,7 @@ func (cmd CmdCat) Usage() string {
func (cmd CmdCat) Execute(args []string) error { func (cmd CmdCat) Execute(args []string) error {
if len(args) < 1 || (args[0] != "masterkey" && args[0] != "config" && len(args) != 2) { if len(args) < 1 || (args[0] != "masterkey" && args[0] != "config" && len(args) != 2) {
return fmt.Errorf("type or ID not specified, Usage: %s", cmd.Usage()) return restic.Fatalf("type or ID not specified, Usage: %s", cmd.Usage())
} }
repo, err := cmd.global.OpenRepository() repo, err := cmd.global.OpenRepository()
@ -54,7 +53,7 @@ func (cmd CmdCat) Execute(args []string) error {
id, err = backend.ParseID(args[1]) id, err = backend.ParseID(args[1])
if err != nil { if err != nil {
if tpe != "snapshot" { if tpe != "snapshot" {
return err return restic.Fatalf("unable to parse ID: %v\n", err)
} }
// find snapshot id with prefix // find snapshot id with prefix
@ -183,7 +182,7 @@ func (cmd CmdCat) Execute(args []string) error {
return err return err
} }
return errors.New("blob not found") return restic.Fatal("blob not found")
case "tree": case "tree":
debug.Log("cat", "cat tree %v", id.Str()) debug.Log("cat", "cat tree %v", id.Str())
@ -204,6 +203,6 @@ func (cmd CmdCat) Execute(args []string) error {
return nil return nil
default: default:
return errors.New("invalid type") return restic.Fatal("invalid type")
} }
} }

View File

@ -1,7 +1,6 @@
package main package main
import ( import (
"errors"
"fmt" "fmt"
"os" "os"
"time" "time"
@ -66,7 +65,7 @@ func (cmd CmdCheck) newReadProgress(todo restic.Stat) *restic.Progress {
func (cmd CmdCheck) Execute(args []string) error { func (cmd CmdCheck) Execute(args []string) error {
if len(args) != 0 { if len(args) != 0 {
return errors.New("check has no arguments") return restic.Fatal("check has no arguments")
} }
repo, err := cmd.global.OpenRepository() repo, err := cmd.global.OpenRepository()
@ -104,7 +103,7 @@ func (cmd CmdCheck) Execute(args []string) error {
for _, err := range errs { for _, err := range errs {
cmd.global.Warnf("error: %v\n", err) cmd.global.Warnf("error: %v\n", err)
} }
return fmt.Errorf("LoadIndex returned errors") return restic.Fatal("LoadIndex returned errors")
} }
done := make(chan struct{}) done := make(chan struct{})
@ -159,7 +158,7 @@ func (cmd CmdCheck) Execute(args []string) error {
} }
if errorsFound { if errorsFound {
return errors.New("repository contains errors") return restic.Fatal("repository contains errors")
} }
return nil return nil
} }

View File

@ -14,8 +14,6 @@ import (
"restic/repository" "restic/repository"
"restic/worker" "restic/worker"
"github.com/juju/errors"
) )
type CmdDump struct { type CmdDump struct {
@ -204,7 +202,7 @@ func (cmd CmdDump) DumpIndexes() error {
func (cmd CmdDump) Execute(args []string) error { func (cmd CmdDump) Execute(args []string) error {
if len(args) != 1 { if len(args) != 1 {
return fmt.Errorf("type not specified, Usage: %s", cmd.Usage()) return restic.Fatalf("type not specified, Usage: %s", cmd.Usage())
} }
repo, err := cmd.global.OpenRepository() repo, err := cmd.global.OpenRepository()
@ -257,6 +255,6 @@ func (cmd CmdDump) Execute(args []string) error {
return nil return nil
default: default:
return errors.Errorf("no such type %q", tpe) return restic.Fatalf("no such type %q", tpe)
} }
} }

View File

@ -1,7 +1,6 @@
package main package main
import ( import (
"fmt"
"path/filepath" "path/filepath"
"time" "time"
@ -57,7 +56,7 @@ func parseTime(str string) (time.Time, error) {
} }
} }
return time.Time{}, fmt.Errorf("unable to parse time: %q", str) return time.Time{}, restic.Fatalf("unable to parse time: %q", str)
} }
func (c CmdFind) findInTree(repo *repository.Repository, id backend.ID, path string) ([]findResult, error) { func (c CmdFind) findInTree(repo *repository.Repository, id backend.ID, path string) ([]findResult, error) {
@ -137,7 +136,7 @@ func (CmdFind) Usage() string {
func (c CmdFind) Execute(args []string) error { func (c CmdFind) Execute(args []string) error {
if len(args) != 1 { if len(args) != 1 {
return fmt.Errorf("wrong number of arguments, Usage: %s", c.Usage()) return restic.Fatalf("wrong number of arguments, Usage: %s", c.Usage())
} }
var err error var err error
@ -177,7 +176,7 @@ func (c CmdFind) Execute(args []string) error {
if c.Snapshot != "" { if c.Snapshot != "" {
snapshotID, err := restic.FindSnapshot(repo, c.Snapshot) snapshotID, err := restic.FindSnapshot(repo, c.Snapshot)
if err != nil { if err != nil {
return fmt.Errorf("invalid id %q: %v", args[1], err) return restic.Fatalf("invalid id %q: %v", args[1], err)
} }
return c.findInSnapshot(repo, snapshotID) return c.findInSnapshot(repo, snapshotID)

View File

@ -1,8 +1,7 @@
package main package main
import ( import (
"errors" "restic"
"restic/repository" "restic/repository"
) )
@ -12,7 +11,7 @@ type CmdInit struct {
func (cmd CmdInit) Execute(args []string) error { func (cmd CmdInit) Execute(args []string) error {
if cmd.global.Repo == "" { if cmd.global.Repo == "" {
return errors.New("Please specify repository location (-r)") return restic.Fatal("Please specify repository location (-r)")
} }
be, err := create(cmd.global.Repo) be, err := create(cmd.global.Repo)

View File

@ -1,8 +1,8 @@
package main package main
import ( import (
"errors"
"fmt" "fmt"
"restic"
"restic/backend" "restic/backend"
"restic/repository" "restic/repository"
@ -69,7 +69,7 @@ func (cmd CmdKey) getNewPassword() string {
func (cmd CmdKey) addKey(repo *repository.Repository) error { func (cmd CmdKey) addKey(repo *repository.Repository) error {
id, err := repository.AddKey(repo, cmd.getNewPassword(), repo.Key()) id, err := repository.AddKey(repo, cmd.getNewPassword(), repo.Key())
if err != nil { if err != nil {
return fmt.Errorf("creating new key failed: %v\n", err) return restic.Fatalf("creating new key failed: %v\n", err)
} }
cmd.global.Verbosef("saved new key as %s\n", id) cmd.global.Verbosef("saved new key as %s\n", id)
@ -79,7 +79,7 @@ func (cmd CmdKey) addKey(repo *repository.Repository) error {
func (cmd CmdKey) deleteKey(repo *repository.Repository, name string) error { func (cmd CmdKey) deleteKey(repo *repository.Repository, name string) error {
if name == repo.KeyName() { if name == repo.KeyName() {
return errors.New("refusing to remove key currently used to access repository") return restic.Fatal("refusing to remove key currently used to access repository")
} }
err := repo.Backend().Remove(backend.Key, name) err := repo.Backend().Remove(backend.Key, name)
@ -94,7 +94,7 @@ func (cmd CmdKey) deleteKey(repo *repository.Repository, name string) error {
func (cmd CmdKey) changePassword(repo *repository.Repository) error { func (cmd CmdKey) changePassword(repo *repository.Repository) error {
id, err := repository.AddKey(repo, cmd.getNewPassword(), repo.Key()) id, err := repository.AddKey(repo, cmd.getNewPassword(), repo.Key())
if err != nil { if err != nil {
return fmt.Errorf("creating new key failed: %v\n", err) return restic.Fatalf("creating new key failed: %v\n", err)
} }
err = repo.Backend().Remove(backend.Key, repo.KeyName()) err = repo.Backend().Remove(backend.Key, repo.KeyName())
@ -113,7 +113,7 @@ func (cmd CmdKey) Usage() string {
func (cmd CmdKey) Execute(args []string) error { func (cmd CmdKey) Execute(args []string) error {
if len(args) < 1 || (args[0] == "rm" && len(args) != 2) { if len(args) < 1 || (args[0] == "rm" && len(args) != 2) {
return fmt.Errorf("wrong number of arguments, Usage: %s", cmd.Usage()) return restic.Fatalf("wrong number of arguments, Usage: %s", cmd.Usage())
} }
repo, err := cmd.global.OpenRepository() repo, err := cmd.global.OpenRepository()

View File

@ -1,9 +1,7 @@
package main package main
import ( import (
"errors" "restic"
"fmt"
"restic/backend" "restic/backend"
) )
@ -27,7 +25,7 @@ func (cmd CmdList) Usage() string {
func (cmd CmdList) Execute(args []string) error { func (cmd CmdList) Execute(args []string) error {
if len(args) != 1 { if len(args) != 1 {
return fmt.Errorf("type not specified, Usage: %s", cmd.Usage()) return restic.Fatalf("type not specified, Usage: %s", cmd.Usage())
} }
repo, err := cmd.global.OpenRepository() repo, err := cmd.global.OpenRepository()
@ -69,7 +67,7 @@ func (cmd CmdList) Execute(args []string) error {
case "locks": case "locks":
t = backend.Lock t = backend.Lock
default: default:
return errors.New("invalid type") return restic.Fatal("invalid type")
} }
for id := range repo.List(t, nil) { for id := range repo.List(t, nil) {

View File

@ -72,7 +72,7 @@ func (cmd CmdLs) Usage() string {
func (cmd CmdLs) Execute(args []string) error { func (cmd CmdLs) Execute(args []string) error {
if len(args) < 1 || len(args) > 2 { if len(args) < 1 || len(args) > 2 {
return fmt.Errorf("wrong number of arguments, Usage: %s", cmd.Usage()) return restic.Fatalf("wrong number of arguments, Usage: %s", cmd.Usage())
} }
repo, err := cmd.global.OpenRepository() repo, err := cmd.global.OpenRepository()

View File

@ -4,8 +4,10 @@
package main package main
import ( import (
"fmt"
"os" "os"
"restic"
"github.com/pkg/errors"
resticfs "restic/fs" resticfs "restic/fs"
"restic/fuse" "restic/fuse"
@ -42,7 +44,7 @@ func (cmd CmdMount) Usage() string {
func (cmd CmdMount) Execute(args []string) error { func (cmd CmdMount) Execute(args []string) error {
if len(args) == 0 { if len(args) == 0 {
return fmt.Errorf("wrong number of parameters, Usage: %s", cmd.Usage()) return restic.Fatalf("wrong number of parameters, Usage: %s", cmd.Usage())
} }
repo, err := cmd.global.OpenRepository() repo, err := cmd.global.OpenRepository()
@ -56,7 +58,7 @@ func (cmd CmdMount) Execute(args []string) error {
} }
mountpoint := args[0] mountpoint := args[0]
if _, err := resticfs.Stat(mountpoint); os.IsNotExist(err) { if _, err := resticfs.Stat(mountpoint); os.IsNotExist(errors.Cause(err)) {
cmd.global.Verbosef("Mountpoint %s doesn't exist, creating it\n", mountpoint) cmd.global.Verbosef("Mountpoint %s doesn't exist, creating it\n", mountpoint)
err = resticfs.Mkdir(mountpoint, os.ModeDir|0700) err = resticfs.Mkdir(mountpoint, os.ModeDir|0700)
if err != nil { if err != nil {

View File

@ -191,7 +191,7 @@ nextPack:
removePacks.Insert(packID) removePacks.Insert(packID)
if !rewritePacks.Has(packID) { if !rewritePacks.Has(packID) {
return fmt.Errorf("pack %v is unneeded, but not contained in rewritePacks", packID.Str()) return restic.Fatalf("pack %v is unneeded, but not contained in rewritePacks", packID.Str())
} }
rewritePacks.Delete(packID) rewritePacks.Delete(packID)

View File

@ -1,9 +1,6 @@
package main package main
import ( import (
"errors"
"fmt"
"restic" "restic"
"restic/backend" "restic/backend"
"restic/debug" "restic/debug"
@ -36,15 +33,15 @@ func (cmd CmdRestore) Usage() string {
func (cmd CmdRestore) Execute(args []string) error { func (cmd CmdRestore) Execute(args []string) error {
if len(args) != 1 { if len(args) != 1 {
return fmt.Errorf("wrong number of arguments, Usage: %s", cmd.Usage()) return restic.Fatalf("wrong number of arguments, Usage: %s", cmd.Usage())
} }
if cmd.Target == "" { if cmd.Target == "" {
return errors.New("please specify a directory to restore to (--target)") return restic.Fatal("please specify a directory to restore to (--target)")
} }
if len(cmd.Exclude) > 0 && len(cmd.Include) > 0 { if len(cmd.Exclude) > 0 && len(cmd.Include) > 0 {
return errors.New("exclude and include patterns are mutually exclusive") return restic.Fatal("exclude and include patterns are mutually exclusive")
} }
snapshotIDString := args[0] snapshotIDString := args[0]

View File

@ -70,7 +70,7 @@ func (cmd CmdSnapshots) Usage() string {
func (cmd CmdSnapshots) Execute(args []string) error { func (cmd CmdSnapshots) Execute(args []string) error {
if len(args) != 0 { if len(args) != 0 {
return fmt.Errorf("wrong number of arguments, usage: %s", cmd.Usage()) return restic.Fatalf("wrong number of arguments, usage: %s", cmd.Usage())
} }
repo, err := cmd.global.OpenRepository() repo, err := cmd.global.OpenRepository()

View File

@ -1,10 +1,10 @@
package main package main
import ( import (
"errors"
"fmt" "fmt"
"io" "io"
"os" "os"
"restic"
"runtime" "runtime"
"strings" "strings"
"syscall" "syscall"
@ -19,6 +19,7 @@ import (
"restic/repository" "restic/repository"
"github.com/jessevdk/go-flags" "github.com/jessevdk/go-flags"
"github.com/pkg/errors"
"golang.org/x/crypto/ssh/terminal" "golang.org/x/crypto/ssh/terminal"
) )
@ -183,7 +184,7 @@ func readPassword(in io.Reader) (password string, err error) {
n, err := io.ReadFull(in, buf) n, err := io.ReadFull(in, buf)
buf = buf[:n] buf = buf[:n]
if err != nil && err != io.ErrUnexpectedEOF { if err != nil && errors.Cause(err) != io.ErrUnexpectedEOF {
return "", err return "", err
} }
@ -246,7 +247,7 @@ const maxKeys = 20
// OpenRepository reads the password and opens the repository. // OpenRepository reads the password and opens the repository.
func (o GlobalOptions) OpenRepository() (*repository.Repository, error) { func (o GlobalOptions) OpenRepository() (*repository.Repository, error) {
if o.Repo == "" { if o.Repo == "" {
return nil, errors.New("Please specify repository location (-r)") return nil, restic.Fatal("Please specify repository location (-r)")
} }
be, err := open(o.Repo) be, err := open(o.Repo)
@ -262,7 +263,7 @@ func (o GlobalOptions) OpenRepository() (*repository.Repository, error) {
err = s.SearchKey(o.password, maxKeys) err = s.SearchKey(o.password, maxKeys)
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to open repo: %v", err) return nil, restic.Fatalf("unable to open repo: %v", err)
} }
return s, nil return s, nil
@ -300,7 +301,7 @@ func open(s string) (backend.Backend, error) {
} }
debug.Log("open", "invalid repository location: %v", s) debug.Log("open", "invalid repository location: %v", s)
return nil, fmt.Errorf("invalid scheme %q", loc.Scheme) return nil, restic.Fatalf("invalid scheme %q", loc.Scheme)
} }
// Create the backend specified by URI. // Create the backend specified by URI.
@ -335,5 +336,5 @@ func create(s string) (backend.Backend, error) {
} }
debug.Log("open", "invalid repository scheme: %v", s) debug.Log("open", "invalid repository scheme: %v", s)
return nil, fmt.Errorf("invalid scheme %q", loc.Scheme) return nil, restic.Fatalf("invalid scheme %q", loc.Scheme)
} }

View File

@ -4,13 +4,14 @@
package main package main
import ( import (
"fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"testing" "testing"
"time" "time"
"github.com/pkg/errors"
"restic" "restic"
"restic/backend" "restic/backend"
"restic/repository" "restic/repository"
@ -50,7 +51,7 @@ func waitForMount(dir string) error {
time.Sleep(mountSleep) time.Sleep(mountSleep)
} }
return fmt.Errorf("subdir %q of dir %s never appeared", mountTestSubdir, dir) return restic.Fatalf("subdir %q of dir %s never appeared", mountTestSubdir, dir)
} }
func cmdMount(t testing.TB, global GlobalOptions, dir string, ready, done chan struct{}) { func cmdMount(t testing.TB, global GlobalOptions, dir string, ready, done chan struct{}) {
@ -126,7 +127,7 @@ func TestMount(t *testing.T) {
datafile := filepath.Join("testdata", "backup-data.tar.gz") datafile := filepath.Join("testdata", "backup-data.tar.gz")
fd, err := os.Open(datafile) fd, err := os.Open(datafile)
if os.IsNotExist(err) { if os.IsNotExist(errors.Cause(err)) {
t.Skipf("unable to find data file %q, skipping", datafile) t.Skipf("unable to find data file %q, skipping", datafile)
return return
} }

View File

@ -10,11 +10,14 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"regexp" "regexp"
"restic"
"strings" "strings"
"syscall" "syscall"
"testing" "testing"
"time" "time"
"github.com/pkg/errors"
"restic/backend" "restic/backend"
"restic/debug" "restic/debug"
"restic/filter" "restic/filter"
@ -141,7 +144,7 @@ func TestBackup(t *testing.T) {
withTestEnvironment(t, func(env *testEnvironment, global GlobalOptions) { withTestEnvironment(t, func(env *testEnvironment, global GlobalOptions) {
datafile := filepath.Join("testdata", "backup-data.tar.gz") datafile := filepath.Join("testdata", "backup-data.tar.gz")
fd, err := os.Open(datafile) fd, err := os.Open(datafile)
if os.IsNotExist(err) { if os.IsNotExist(errors.Cause(err)) {
t.Skipf("unable to find data file %q, skipping", datafile) t.Skipf("unable to find data file %q, skipping", datafile)
return return
} }
@ -203,7 +206,7 @@ func TestBackupNonExistingFile(t *testing.T) {
withTestEnvironment(t, func(env *testEnvironment, global GlobalOptions) { withTestEnvironment(t, func(env *testEnvironment, global GlobalOptions) {
datafile := filepath.Join("testdata", "backup-data.tar.gz") datafile := filepath.Join("testdata", "backup-data.tar.gz")
fd, err := os.Open(datafile) fd, err := os.Open(datafile)
if os.IsNotExist(err) { if os.IsNotExist(errors.Cause(err)) {
t.Skipf("unable to find data file %q, skipping", datafile) t.Skipf("unable to find data file %q, skipping", datafile)
return return
} }
@ -231,7 +234,7 @@ func TestBackupMissingFile1(t *testing.T) {
withTestEnvironment(t, func(env *testEnvironment, global GlobalOptions) { withTestEnvironment(t, func(env *testEnvironment, global GlobalOptions) {
datafile := filepath.Join("testdata", "backup-data.tar.gz") datafile := filepath.Join("testdata", "backup-data.tar.gz")
fd, err := os.Open(datafile) fd, err := os.Open(datafile)
if os.IsNotExist(err) { if os.IsNotExist(errors.Cause(err)) {
t.Skipf("unable to find data file %q, skipping", datafile) t.Skipf("unable to find data file %q, skipping", datafile)
return return
} }
@ -269,7 +272,7 @@ func TestBackupMissingFile2(t *testing.T) {
withTestEnvironment(t, func(env *testEnvironment, global GlobalOptions) { withTestEnvironment(t, func(env *testEnvironment, global GlobalOptions) {
datafile := filepath.Join("testdata", "backup-data.tar.gz") datafile := filepath.Join("testdata", "backup-data.tar.gz")
fd, err := os.Open(datafile) fd, err := os.Open(datafile)
if os.IsNotExist(err) { if os.IsNotExist(errors.Cause(err)) {
t.Skipf("unable to find data file %q, skipping", datafile) t.Skipf("unable to find data file %q, skipping", datafile)
return return
} }
@ -307,7 +310,7 @@ func TestBackupDirectoryError(t *testing.T) {
withTestEnvironment(t, func(env *testEnvironment, global GlobalOptions) { withTestEnvironment(t, func(env *testEnvironment, global GlobalOptions) {
datafile := filepath.Join("testdata", "backup-data.tar.gz") datafile := filepath.Join("testdata", "backup-data.tar.gz")
fd, err := os.Open(datafile) fd, err := os.Open(datafile)
if os.IsNotExist(err) { if os.IsNotExist(errors.Cause(err)) {
t.Skipf("unable to find data file %q, skipping", datafile) t.Skipf("unable to find data file %q, skipping", datafile)
return return
} }
@ -579,7 +582,7 @@ func testFileSize(filename string, size int64) error {
} }
if fi.Size() != size { if fi.Size() != size {
return fmt.Errorf("wrong file size for %v: expected %v, got %v", filename, size, fi.Size()) return restic.Fatalf("wrong file size for %v: expected %v, got %v", filename, size, fi.Size())
} }
return nil return nil
@ -624,7 +627,7 @@ func TestRestoreFilter(t *testing.T) {
if ok, _ := filter.Match(pat, filepath.Base(test.name)); !ok { if ok, _ := filter.Match(pat, filepath.Base(test.name)); !ok {
OK(t, err) OK(t, err)
} else { } else {
Assert(t, os.IsNotExist(err), Assert(t, os.IsNotExist(errors.Cause(err)),
"expected %v to not exist in restore step %v, but it exists, err %v", test.name, i+1, err) "expected %v to not exist in restore step %v, but it exists, err %v", test.name, i+1, err)
} }
} }
@ -672,15 +675,15 @@ func TestRestoreLatest(t *testing.T) {
cmdRestoreLatest(t, global, filepath.Join(env.base, "restore1"), []string{filepath.Dir(p1)}, "") cmdRestoreLatest(t, global, filepath.Join(env.base, "restore1"), []string{filepath.Dir(p1)}, "")
OK(t, testFileSize(p1rAbs, int64(102))) OK(t, testFileSize(p1rAbs, int64(102)))
if _, err := os.Stat(p2rAbs); os.IsNotExist(err) { if _, err := os.Stat(p2rAbs); os.IsNotExist(errors.Cause(err)) {
Assert(t, os.IsNotExist(err), Assert(t, os.IsNotExist(errors.Cause(err)),
"expected %v to not exist in restore, but it exists, err %v", p2rAbs, err) "expected %v to not exist in restore, but it exists, err %v", p2rAbs, err)
} }
cmdRestoreLatest(t, global, filepath.Join(env.base, "restore2"), []string{filepath.Dir(p2)}, "") cmdRestoreLatest(t, global, filepath.Join(env.base, "restore2"), []string{filepath.Dir(p2)}, "")
OK(t, testFileSize(p2rAbs, int64(103))) OK(t, testFileSize(p2rAbs, int64(103)))
if _, err := os.Stat(p1rAbs); os.IsNotExist(err) { if _, err := os.Stat(p1rAbs); os.IsNotExist(errors.Cause(err)) {
Assert(t, os.IsNotExist(err), Assert(t, os.IsNotExist(errors.Cause(err)),
"expected %v to not exist in restore, but it exists, err %v", p1rAbs, err) "expected %v to not exist in restore, but it exists, err %v", p1rAbs, err)
} }

View File

@ -2,11 +2,13 @@ package main
import ( import (
"fmt" "fmt"
"github.com/jessevdk/go-flags"
"os" "os"
"restic" "restic"
"restic/debug" "restic/debug"
"runtime" "runtime"
"github.com/jessevdk/go-flags"
"github.com/pkg/errors"
) )
func init() { func init() {
@ -35,12 +37,15 @@ func main() {
os.Exit(0) os.Exit(0)
} }
if err != nil { debug.Log("main", "command returned error: %#v", err)
fmt.Fprintf(os.Stderr, "%v\n", err)
}
if restic.IsAlreadyLocked(err) { switch {
fmt.Fprintf(os.Stderr, "\nthe `unlock` command can be used to remove stale locks\n") case restic.IsAlreadyLocked(errors.Cause(err)):
fmt.Fprintf(os.Stderr, "%v\nthe `unlock` command can be used to remove stale locks\n", err)
case restic.IsFatal(errors.Cause(err)):
fmt.Fprintf(os.Stderr, "%v\n", err)
case err != nil:
fmt.Fprintf(os.Stderr, "%+v\n", err)
} }
RunCleanupHandlers() RunCleanupHandlers()

View File

@ -9,6 +9,7 @@ import (
"restic/repository" "restic/repository"
"time" "time"
"github.com/pkg/errors"
"github.com/restic/chunker" "github.com/restic/chunker"
) )
@ -16,7 +17,7 @@ import (
func saveTreeJSON(repo *repository.Repository, item interface{}) (backend.ID, error) { func saveTreeJSON(repo *repository.Repository, item interface{}) (backend.ID, error) {
data, err := json.Marshal(item) data, err := json.Marshal(item)
if err != nil { if err != nil {
return backend.ID{}, err return backend.ID{}, errors.Wrap(err, "")
} }
data = append(data, '\n') data = append(data, '\n')
@ -48,12 +49,12 @@ func ArchiveReader(repo *repository.Repository, p *Progress, rd io.Reader, name
for { for {
chunk, err := chnker.Next(getBuf()) chunk, err := chnker.Next(getBuf())
if err == io.EOF { if errors.Cause(err) == io.EOF {
break break
} }
if err != nil { if err != nil {
return nil, backend.ID{}, err return nil, backend.ID{}, errors.Wrap(err, "chunker.Next()")
} }
id := backend.Hash(chunk.Data) id := backend.Hash(chunk.Data)

View File

@ -48,6 +48,9 @@ func checkSavedFile(t *testing.T, repo *repository.Repository, treeID backend.ID
buf2 = buf2[:len(buf)] buf2 = buf2[:len(buf)]
_, err = io.ReadFull(rd, buf2) _, err = io.ReadFull(rd, buf2)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(buf, buf2) { if !bytes.Equal(buf, buf2) {
t.Fatalf("blob %d (%v) is wrong", i, id.Str()) t.Fatalf("blob %d (%v) is wrong", i, id.Str())

View File

@ -10,6 +10,8 @@ import (
"sync" "sync"
"time" "time"
"github.com/pkg/errors"
"restic/backend" "restic/backend"
"restic/debug" "restic/debug"
"restic/fs" "restic/fs"
@ -18,8 +20,6 @@ import (
"restic/repository" "restic/repository"
"github.com/restic/chunker" "github.com/restic/chunker"
"github.com/juju/errors"
) )
const ( const (
@ -113,7 +113,7 @@ func (arch *Archiver) Save(t pack.BlobType, data []byte, id backend.ID) error {
func (arch *Archiver) SaveTreeJSON(item interface{}) (backend.ID, error) { func (arch *Archiver) SaveTreeJSON(item interface{}) (backend.ID, error) {
data, err := json.Marshal(item) data, err := json.Marshal(item)
if err != nil { if err != nil {
return backend.ID{}, err return backend.ID{}, errors.Wrap(err, "Marshal")
} }
data = append(data, '\n') data = append(data, '\n')
@ -129,7 +129,7 @@ func (arch *Archiver) SaveTreeJSON(item interface{}) (backend.ID, error) {
func (arch *Archiver) reloadFileIfChanged(node *Node, file fs.File) (*Node, error) { func (arch *Archiver) reloadFileIfChanged(node *Node, file fs.File) (*Node, error) {
fi, err := file.Stat() fi, err := file.Stat()
if err != nil { if err != nil {
return nil, err return nil, errors.Wrap(err, "Stat")
} }
if fi.ModTime() == node.ModTime { if fi.ModTime() == node.ModTime {
@ -178,7 +178,7 @@ func waitForResults(resultChannels [](<-chan saveResult)) ([]saveResult, error)
} }
if len(results) != len(resultChannels) { if len(results) != len(resultChannels) {
return nil, fmt.Errorf("chunker returned %v chunks, but only %v blobs saved", len(resultChannels), len(results)) return nil, errors.Errorf("chunker returned %v chunks, but only %v blobs saved", len(resultChannels), len(results))
} }
return results, nil return results, nil
@ -198,7 +198,7 @@ func updateNodeContent(node *Node, results []saveResult) error {
} }
if bytes != node.Size { if bytes != node.Size {
return fmt.Errorf("errors saving node %q: saved %d bytes, wanted %d bytes", node.path, bytes, node.Size) return errors.Errorf("errors saving node %q: saved %d bytes, wanted %d bytes", node.path, bytes, node.Size)
} }
debug.Log("Archiver.SaveFile", "SaveFile(%q): %v blobs\n", node.path, len(results)) debug.Log("Archiver.SaveFile", "SaveFile(%q): %v blobs\n", node.path, len(results))
@ -212,7 +212,7 @@ func (arch *Archiver) SaveFile(p *Progress, node *Node) error {
file, err := fs.Open(node.path) file, err := fs.Open(node.path)
defer file.Close() defer file.Close()
if err != nil { if err != nil {
return err return errors.Wrap(err, "Open")
} }
node, err = arch.reloadFileIfChanged(node, file) node, err = arch.reloadFileIfChanged(node, file)
@ -225,12 +225,12 @@ func (arch *Archiver) SaveFile(p *Progress, node *Node) error {
for { for {
chunk, err := chnker.Next(getBuf()) chunk, err := chnker.Next(getBuf())
if err == io.EOF { if errors.Cause(err) == io.EOF {
break break
} }
if err != nil { if err != nil {
return errors.Annotate(err, "SaveFile() chunker.Next()") return errors.Wrap(err, "chunker.Next")
} }
resCh := make(chan saveResult, 1) resCh := make(chan saveResult, 1)
@ -819,7 +819,7 @@ func Scan(dirs []string, filter pipe.SelectFunc, p *Progress) (Stat, error) {
debug.Log("Scan", "Done for %v, err: %v", dir, err) debug.Log("Scan", "Done for %v, err: %v", dir, err)
if err != nil { if err != nil {
return Stat{}, err return Stat{}, errors.Wrap(err, "fs.Walk")
} }
} }

View File

@ -2,13 +2,14 @@ package restic_test
import ( import (
"crypto/rand" "crypto/rand"
"errors"
"io" "io"
mrand "math/rand" mrand "math/rand"
"sync" "sync"
"testing" "testing"
"time" "time"
"github.com/pkg/errors"
"restic" "restic"
"restic/backend" "restic/backend"
"restic/pack" "restic/pack"

View File

@ -14,6 +14,7 @@ import (
"restic/repository" "restic/repository"
. "restic/test" . "restic/test"
"github.com/pkg/errors"
"github.com/restic/chunker" "github.com/restic/chunker"
) )
@ -31,7 +32,7 @@ func benchmarkChunkEncrypt(b testing.TB, buf, buf2 []byte, rd Rdr, key *crypto.K
for { for {
chunk, err := ch.Next(buf) chunk, err := ch.Next(buf)
if err == io.EOF { if errors.Cause(err) == io.EOF {
break break
} }
@ -69,7 +70,7 @@ func benchmarkChunkEncryptP(b *testing.PB, buf []byte, rd Rdr, key *crypto.Key)
for { for {
chunk, err := ch.Next(buf) chunk, err := ch.Next(buf)
if err == io.EOF { if errors.Cause(err) == io.EOF {
break break
} }
@ -292,7 +293,7 @@ func getRandomData(seed int, size int) []chunker.Chunk {
for { for {
c, err := chunker.Next(nil) c, err := chunker.Next(nil)
if err == io.EOF { if errors.Cause(err) == io.EOF {
break break
} }
chunks = append(chunks, c) chunks = append(chunks, c)

View File

@ -1,6 +1,6 @@
package backend package backend
import "errors" import "github.com/pkg/errors"
// ErrNoIDPrefixFound is returned by Find() when no ID for the given prefix // ErrNoIDPrefixFound is returned by Find() when no ID for the given prefix
// could be found. // could be found.

View File

@ -1,8 +1,9 @@
package backend package backend
import ( import (
"errors"
"fmt" "fmt"
"github.com/pkg/errors"
) )
// Handle is used to store and access data in a backend. // Handle is used to store and access data in a backend.
@ -33,7 +34,7 @@ func (h Handle) Valid() error {
case Index: case Index:
case Config: case Config:
default: default:
return fmt.Errorf("invalid Type %q", h.Type) return errors.Errorf("invalid Type %q", h.Type)
} }
if h.Type == Config { if h.Type == Config {

View File

@ -5,7 +5,8 @@ import (
"crypto/sha256" "crypto/sha256"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"errors"
"github.com/pkg/errors"
) )
// Hash returns the ID for data. // Hash returns the ID for data.
@ -24,7 +25,7 @@ func ParseID(s string) (ID, error) {
b, err := hex.DecodeString(s) b, err := hex.DecodeString(s)
if err != nil { if err != nil {
return ID{}, err return ID{}, errors.Wrap(err, "hex.DecodeString")
} }
if len(b) != IDSize { if len(b) != IDSize {
@ -72,7 +73,7 @@ func (id ID) Equal(other ID) bool {
func (id ID) EqualString(other string) (bool, error) { func (id ID) EqualString(other string) (bool, error) {
s, err := hex.DecodeString(other) s, err := hex.DecodeString(other)
if err != nil { if err != nil {
return false, err return false, errors.Wrap(err, "hex.DecodeString")
} }
id2 := ID{} id2 := ID{}
@ -96,12 +97,12 @@ func (id *ID) UnmarshalJSON(b []byte) error {
var s string var s string
err := json.Unmarshal(b, &s) err := json.Unmarshal(b, &s)
if err != nil { if err != nil {
return err return errors.Wrap(err, "Unmarshal")
} }
_, err = hex.Decode(id[:], []byte(s)) _, err = hex.Decode(id[:], []byte(s))
if err != nil { if err != nil {
return err return errors.Wrap(err, "hex.Decode")
} }
return nil return nil

View File

@ -1,8 +1,9 @@
package local package local
import ( import (
"errors"
"strings" "strings"
"github.com/pkg/errors"
) )
// ParseConfig parses a local backend config. // ParseConfig parses a local backend config.

View File

@ -1,13 +1,13 @@
package local package local
import ( import (
"errors"
"fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"github.com/pkg/errors"
"restic/backend" "restic/backend"
"restic/debug" "restic/debug"
"restic/fs" "restic/fs"
@ -35,7 +35,7 @@ func Open(dir string) (*Local, error) {
// test if all necessary dirs are there // test if all necessary dirs are there
for _, d := range paths(dir) { for _, d := range paths(dir) {
if _, err := fs.Stat(d); err != nil { if _, err := fs.Stat(d); err != nil {
return nil, fmt.Errorf("%s does not exist", d) return nil, errors.Wrap(err, "Open")
} }
} }
@ -55,7 +55,7 @@ func Create(dir string) (*Local, error) {
for _, d := range paths(dir) { for _, d := range paths(dir) {
err := fs.MkdirAll(d, backend.Modes.Dir) err := fs.MkdirAll(d, backend.Modes.Dir)
if err != nil { if err != nil {
return nil, err return nil, errors.Wrap(err, "MkdirAll")
} }
} }
@ -110,13 +110,13 @@ func (b *Local) Load(h backend.Handle, p []byte, off int64) (n int, err error) {
f, err := fs.Open(filename(b.p, h.Type, h.Name)) f, err := fs.Open(filename(b.p, h.Type, h.Name))
if err != nil { if err != nil {
return 0, err return 0, errors.Wrap(err, "Open")
} }
defer func() { defer func() {
e := f.Close() e := f.Close()
if err == nil && e != nil { if err == nil && e != nil {
err = e err = errors.Wrap(e, "Close")
} }
}() }()
@ -128,7 +128,7 @@ func (b *Local) Load(h backend.Handle, p []byte, off int64) (n int, err error) {
} }
if err != nil { if err != nil {
return 0, err return 0, errors.Wrap(err, "Seek")
} }
return io.ReadFull(f, p) return io.ReadFull(f, p)
@ -138,12 +138,12 @@ func (b *Local) Load(h backend.Handle, p []byte, off int64) (n int, err error) {
func writeToTempfile(tempdir string, p []byte) (filename string, err error) { func writeToTempfile(tempdir string, p []byte) (filename string, err error) {
tmpfile, err := ioutil.TempFile(tempdir, "temp-") tmpfile, err := ioutil.TempFile(tempdir, "temp-")
if err != nil { if err != nil {
return "", err return "", errors.Wrap(err, "TempFile")
} }
n, err := tmpfile.Write(p) n, err := tmpfile.Write(p)
if err != nil { if err != nil {
return "", err return "", errors.Wrap(err, "Write")
} }
if n != len(p) { if n != len(p) {
@ -151,17 +151,17 @@ func writeToTempfile(tempdir string, p []byte) (filename string, err error) {
} }
if err = tmpfile.Sync(); err != nil { if err = tmpfile.Sync(); err != nil {
return "", err return "", errors.Wrap(err, "Syncn")
} }
err = fs.ClearCache(tmpfile) err = fs.ClearCache(tmpfile)
if err != nil { if err != nil {
return "", err return "", errors.Wrap(err, "ClearCache")
} }
err = tmpfile.Close() err = tmpfile.Close()
if err != nil { if err != nil {
return "", err return "", errors.Wrap(err, "Close")
} }
return tmpfile.Name(), nil return tmpfile.Name(), nil
@ -184,14 +184,14 @@ func (b *Local) Save(h backend.Handle, p []byte) (err error) {
// test if new path already exists // test if new path already exists
if _, err := fs.Stat(filename); err == nil { if _, err := fs.Stat(filename); err == nil {
return fmt.Errorf("Rename(): file %v already exists", filename) return errors.Errorf("Rename(): file %v already exists", filename)
} }
// create directories if necessary, ignore errors // create directories if necessary, ignore errors
if h.Type == backend.Data { if h.Type == backend.Data {
err = fs.MkdirAll(filepath.Dir(filename), backend.Modes.Dir) err = fs.MkdirAll(filepath.Dir(filename), backend.Modes.Dir)
if err != nil { if err != nil {
return err return errors.Wrap(err, "MkdirAll")
} }
} }
@ -200,13 +200,13 @@ func (b *Local) Save(h backend.Handle, p []byte) (err error) {
h, filepath.Base(tmpfile), filepath.Base(filename), err) h, filepath.Base(tmpfile), filepath.Base(filename), err)
if err != nil { if err != nil {
return err return errors.Wrap(err, "Rename")
} }
// set mode to read-only // set mode to read-only
fi, err := fs.Stat(filename) fi, err := fs.Stat(filename)
if err != nil { if err != nil {
return err return errors.Wrap(err, "Stat")
} }
return setNewFileMode(filename, fi) return setNewFileMode(filename, fi)
@ -221,7 +221,7 @@ func (b *Local) Stat(h backend.Handle) (backend.BlobInfo, error) {
fi, err := fs.Stat(filename(b.p, h.Type, h.Name)) fi, err := fs.Stat(filename(b.p, h.Type, h.Name))
if err != nil { if err != nil {
return backend.BlobInfo{}, err return backend.BlobInfo{}, errors.Wrap(err, "Stat")
} }
return backend.BlobInfo{Size: fi.Size()}, nil return backend.BlobInfo{Size: fi.Size()}, nil
@ -232,10 +232,10 @@ func (b *Local) Test(t backend.Type, name string) (bool, error) {
debug.Log("backend.local.Test", "Test %v %v", t, name) debug.Log("backend.local.Test", "Test %v %v", t, name)
_, err := fs.Stat(filename(b.p, t, name)) _, err := fs.Stat(filename(b.p, t, name))
if err != nil { if err != nil {
if os.IsNotExist(err) { if os.IsNotExist(errors.Cause(err)) {
return false, nil return false, nil
} }
return false, err return false, errors.Wrap(err, "Stat")
} }
return true, nil return true, nil
@ -249,7 +249,7 @@ func (b *Local) Remove(t backend.Type, name string) error {
// reset read-only flag // reset read-only flag
err := fs.Chmod(fn, 0666) err := fs.Chmod(fn, 0666)
if err != nil { if err != nil {
return err return errors.Wrap(err, "Chmod")
} }
return fs.Remove(fn) return fs.Remove(fn)
@ -262,13 +262,13 @@ func isFile(fi os.FileInfo) bool {
func readdir(d string) (fileInfos []os.FileInfo, err error) { func readdir(d string) (fileInfos []os.FileInfo, err error) {
f, e := fs.Open(d) f, e := fs.Open(d)
if e != nil { if e != nil {
return nil, e return nil, errors.Wrap(e, "Open")
} }
defer func() { defer func() {
e := f.Close() e := f.Close()
if err == nil { if err == nil {
err = e err = errors.Wrap(e, "Close")
} }
}() }()

View File

@ -1,10 +1,11 @@
package mem package mem
import ( import (
"errors"
"io" "io"
"sync" "sync"
"github.com/pkg/errors"
"restic/backend" "restic/backend"
"restic/debug" "restic/debug"
) )

View File

@ -1,7 +1,7 @@
package mem_test package mem_test
import ( import (
"errors" "github.com/pkg/errors"
"restic/backend" "restic/backend"
"restic/backend/mem" "restic/backend/mem"

View File

@ -1,6 +1,6 @@
package backend package backend
import "errors" import "github.com/pkg/errors"
// MockBackend implements a backend whose functions can be specified. This // MockBackend implements a backend whose functions can be specified. This
// should only be used for tests. // should only be used for tests.

View File

@ -1,9 +1,10 @@
package rest package rest
import ( import (
"errors"
"net/url" "net/url"
"strings" "strings"
"github.com/pkg/errors"
) )
// Config contains all configuration necessary to connect to a REST server. // Config contains all configuration necessary to connect to a REST server.
@ -21,7 +22,7 @@ func ParseConfig(s string) (interface{}, error) {
u, err := url.Parse(s) u, err := url.Parse(s)
if err != nil { if err != nil {
return nil, err return nil, errors.Wrap(err, "url.Parse")
} }
cfg := Config{URL: u} cfg := Config{URL: u}

View File

@ -3,7 +3,6 @@ package rest
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
@ -11,6 +10,8 @@ import (
"path" "path"
"strings" "strings"
"github.com/pkg/errors"
"restic/backend" "restic/backend"
) )
@ -79,7 +80,7 @@ func (b *restBackend) Load(h backend.Handle, p []byte, off int64) (n int, err er
if off < 0 { if off < 0 {
info, err := b.Stat(h) info, err := b.Stat(h)
if err != nil { if err != nil {
return 0, err return 0, errors.Wrap(err, "Stat")
} }
if -off > info.Size { if -off > info.Size {
@ -91,7 +92,7 @@ func (b *restBackend) Load(h backend.Handle, p []byte, off int64) (n int, err er
req, err := http.NewRequest("GET", restPath(b.url, h), nil) req, err := http.NewRequest("GET", restPath(b.url, h), nil)
if err != nil { if err != nil {
return 0, err return 0, errors.Wrap(err, "http.NewRequest")
} }
req.Header.Add("Range", fmt.Sprintf("bytes=%d-%d", off, off+int64(len(p)))) req.Header.Add("Range", fmt.Sprintf("bytes=%d-%d", off, off+int64(len(p))))
<-b.connChan <-b.connChan
@ -103,16 +104,16 @@ func (b *restBackend) Load(h backend.Handle, p []byte, off int64) (n int, err er
e := resp.Body.Close() e := resp.Body.Close()
if err == nil { if err == nil {
err = e err = errors.Wrap(e, "Close")
} }
}() }()
} }
if err != nil { if err != nil {
return 0, err return 0, errors.Wrap(err, "client.Do")
} }
if resp.StatusCode != 200 && resp.StatusCode != 206 { if resp.StatusCode != 200 && resp.StatusCode != 206 {
return 0, fmt.Errorf("unexpected HTTP response code %v", resp.StatusCode) return 0, errors.Errorf("unexpected HTTP response code %v", resp.StatusCode)
} }
return io.ReadFull(resp.Body, p) return io.ReadFull(resp.Body, p)
@ -133,17 +134,17 @@ func (b *restBackend) Save(h backend.Handle, p []byte) (err error) {
e := resp.Body.Close() e := resp.Body.Close()
if err == nil { if err == nil {
err = e err = errors.Wrap(e, "Close")
} }
}() }()
} }
if err != nil { if err != nil {
return err return errors.Wrap(err, "client.Post")
} }
if resp.StatusCode != 200 { if resp.StatusCode != 200 {
return fmt.Errorf("unexpected HTTP response code %v", resp.StatusCode) return errors.Errorf("unexpected HTTP response code %v", resp.StatusCode)
} }
return nil return nil
@ -159,15 +160,15 @@ func (b *restBackend) Stat(h backend.Handle) (backend.BlobInfo, error) {
resp, err := b.client.Head(restPath(b.url, h)) resp, err := b.client.Head(restPath(b.url, h))
b.connChan <- struct{}{} b.connChan <- struct{}{}
if err != nil { if err != nil {
return backend.BlobInfo{}, err return backend.BlobInfo{}, errors.Wrap(err, "client.Head")
} }
if err = resp.Body.Close(); err != nil { if err = resp.Body.Close(); err != nil {
return backend.BlobInfo{}, err return backend.BlobInfo{}, errors.Wrap(err, "Close")
} }
if resp.StatusCode != 200 { if resp.StatusCode != 200 {
return backend.BlobInfo{}, fmt.Errorf("unexpected HTTP response code %v", resp.StatusCode) return backend.BlobInfo{}, errors.Errorf("unexpected HTTP response code %v", resp.StatusCode)
} }
if resp.ContentLength < 0 { if resp.ContentLength < 0 {
@ -200,14 +201,14 @@ func (b *restBackend) Remove(t backend.Type, name string) error {
req, err := http.NewRequest("DELETE", restPath(b.url, h), nil) req, err := http.NewRequest("DELETE", restPath(b.url, h), nil)
if err != nil { if err != nil {
return err return errors.Wrap(err, "http.NewRequest")
} }
<-b.connChan <-b.connChan
resp, err := b.client.Do(req) resp, err := b.client.Do(req)
b.connChan <- struct{}{} b.connChan <- struct{}{}
if err != nil { if err != nil {
return err return errors.Wrap(err, "client.Do")
} }
if resp.StatusCode != 200 { if resp.StatusCode != 200 {

View File

@ -1,11 +1,12 @@
package rest_test package rest_test
import ( import (
"errors"
"fmt" "fmt"
"net/url" "net/url"
"os" "os"
"github.com/pkg/errors"
"restic/backend" "restic/backend"
"restic/backend/rest" "restic/backend/rest"
"restic/backend/test" "restic/backend/test"

View File

@ -1,10 +1,11 @@
package s3 package s3
import ( import (
"errors"
"net/url" "net/url"
"path" "path"
"strings" "strings"
"github.com/pkg/errors"
) )
// Config contains all configuration necessary to connect to an s3 compatible // Config contains all configuration necessary to connect to an s3 compatible
@ -31,7 +32,7 @@ func ParseConfig(s string) (interface{}, error) {
// bucket name and prefix // bucket name and prefix
url, err := url.Parse(s[3:]) url, err := url.Parse(s[3:])
if err != nil { if err != nil {
return nil, err return nil, errors.Wrap(err, "url.Parse")
} }
if url.Path == "" { if url.Path == "" {

View File

@ -2,10 +2,11 @@ package s3
import ( import (
"bytes" "bytes"
"errors"
"io" "io"
"strings" "strings"
"github.com/pkg/errors"
"github.com/minio/minio-go" "github.com/minio/minio-go"
"restic/backend" "restic/backend"
@ -29,7 +30,7 @@ func Open(cfg Config) (backend.Backend, error) {
client, err := minio.New(cfg.Endpoint, cfg.KeyID, cfg.Secret, !cfg.UseHTTP) client, err := minio.New(cfg.Endpoint, cfg.KeyID, cfg.Secret, !cfg.UseHTTP)
if err != nil { if err != nil {
return nil, err return nil, errors.Wrap(err, "minio.New")
} }
be := &s3{client: client, bucketname: cfg.Bucket, prefix: cfg.Prefix} be := &s3{client: client, bucketname: cfg.Bucket, prefix: cfg.Prefix}
@ -38,14 +39,14 @@ func Open(cfg Config) (backend.Backend, error) {
ok, err := client.BucketExists(cfg.Bucket) ok, err := client.BucketExists(cfg.Bucket)
if err != nil { if err != nil {
debug.Log("s3.Open", "BucketExists(%v) returned err %v, trying to create the bucket", cfg.Bucket, err) debug.Log("s3.Open", "BucketExists(%v) returned err %v, trying to create the bucket", cfg.Bucket, err)
return nil, err return nil, errors.Wrap(err, "client.BucketExists")
} }
if !ok { if !ok {
// create new bucket with default ACL in default region // create new bucket with default ACL in default region
err = client.MakeBucket(cfg.Bucket, "") err = client.MakeBucket(cfg.Bucket, "")
if err != nil { if err != nil {
return nil, err return nil, errors.Wrap(err, "client.MakeBucket")
} }
} }
@ -94,20 +95,20 @@ func (be s3) Load(h backend.Handle, p []byte, off int64) (n int, err error) {
obj, err = be.client.GetObject(be.bucketname, path) obj, err = be.client.GetObject(be.bucketname, path)
if err != nil { if err != nil {
debug.Log("s3.Load", " err %v", err) debug.Log("s3.Load", " err %v", err)
return 0, err return 0, errors.Wrap(err, "client.GetObject")
} }
// make sure that the object is closed properly. // make sure that the object is closed properly.
defer func() { defer func() {
e := obj.Close() e := obj.Close()
if err == nil { if err == nil {
err = e err = errors.Wrap(e, "Close")
} }
}() }()
info, err := obj.Stat() info, err := obj.Stat()
if err != nil { if err != nil {
return 0, err return 0, errors.Wrap(err, "obj.Stat")
} }
// handle negative offsets // handle negative offsets
@ -124,7 +125,7 @@ func (be s3) Load(h backend.Handle, p []byte, off int64) (n int, err error) {
// return an error if the offset is beyond the end of the file // return an error if the offset is beyond the end of the file
if off > info.Size { if off > info.Size {
return 0, io.EOF return 0, errors.Wrap(io.EOF, "")
} }
var nextError error var nextError error
@ -140,7 +141,7 @@ func (be s3) Load(h backend.Handle, p []byte, off int64) (n int, err error) {
} }
n, err = obj.ReadAt(p, off) n, err = obj.ReadAt(p, off)
if int64(n) == info.Size-off && err == io.EOF { if int64(n) == info.Size-off && errors.Cause(err) == io.EOF {
err = nil err = nil
} }
@ -178,7 +179,7 @@ func (be s3) Save(h backend.Handle, p []byte) (err error) {
n, err := be.client.PutObject(be.bucketname, path, bytes.NewReader(p), "binary/octet-stream") n, err := be.client.PutObject(be.bucketname, path, bytes.NewReader(p), "binary/octet-stream")
debug.Log("s3.Save", "%v -> %v bytes, err %#v", path, n, err) debug.Log("s3.Save", "%v -> %v bytes, err %#v", path, n, err)
return err return errors.Wrap(err, "client.PutObject")
} }
// Stat returns information about a blob. // Stat returns information about a blob.
@ -191,21 +192,21 @@ func (be s3) Stat(h backend.Handle) (bi backend.BlobInfo, err error) {
obj, err = be.client.GetObject(be.bucketname, path) obj, err = be.client.GetObject(be.bucketname, path)
if err != nil { if err != nil {
debug.Log("s3.Stat", "GetObject() err %v", err) debug.Log("s3.Stat", "GetObject() err %v", err)
return backend.BlobInfo{}, err return backend.BlobInfo{}, errors.Wrap(err, "client.GetObject")
} }
// make sure that the object is closed properly. // make sure that the object is closed properly.
defer func() { defer func() {
e := obj.Close() e := obj.Close()
if err == nil { if err == nil {
err = e err = errors.Wrap(e, "Close")
} }
}() }()
fi, err := obj.Stat() fi, err := obj.Stat()
if err != nil { if err != nil {
debug.Log("s3.Stat", "Stat() err %v", err) debug.Log("s3.Stat", "Stat() err %v", err)
return backend.BlobInfo{}, err return backend.BlobInfo{}, errors.Wrap(err, "Stat")
} }
return backend.BlobInfo{Size: fi.Size}, nil return backend.BlobInfo{Size: fi.Size}, nil
@ -229,7 +230,7 @@ func (be *s3) Remove(t backend.Type, name string) error {
path := be.s3path(t, name) path := be.s3path(t, name)
err := be.client.RemoveObject(be.bucketname, path) err := be.client.RemoveObject(be.bucketname, path)
debug.Log("s3.Remove", "%v %v -> err %v", t, name, err) debug.Log("s3.Remove", "%v %v -> err %v", t, name, err)
return err return errors.Wrap(err, "client.RemoveObject")
} }
// List returns a channel that yields all names of blobs of type t. A // List returns a channel that yields all names of blobs of type t. A

View File

@ -1,11 +1,12 @@
package s3_test package s3_test
import ( import (
"errors"
"fmt" "fmt"
"net/url" "net/url"
"os" "os"
"github.com/pkg/errors"
"restic/backend" "restic/backend"
"restic/backend/s3" "restic/backend/s3"
"restic/backend/test" "restic/backend/test"

View File

@ -1,11 +1,11 @@
package sftp package sftp
import ( import (
"errors"
"fmt"
"net/url" "net/url"
"path" "path"
"strings" "strings"
"github.com/pkg/errors"
) )
// Config collects all information required to connect to an sftp server. // Config collects all information required to connect to an sftp server.
@ -26,7 +26,7 @@ func ParseConfig(s string) (interface{}, error) {
// parse the "sftp://user@host/path" url format // parse the "sftp://user@host/path" url format
url, err := url.Parse(s) url, err := url.Parse(s)
if err != nil { if err != nil {
return nil, err return nil, errors.Wrap(err, "url.Parse")
} }
if url.User != nil { if url.User != nil {
user = url.User.Username() user = url.User.Username()
@ -34,7 +34,7 @@ func ParseConfig(s string) (interface{}, error) {
host = url.Host host = url.Host
dir = url.Path dir = url.Path
if dir == "" { if dir == "" {
return nil, fmt.Errorf("invalid backend %q, no directory specified", s) return nil, errors.Errorf("invalid backend %q, no directory specified", s)
} }
dir = dir[1:] dir = dir[1:]

View File

@ -12,10 +12,11 @@ import (
"strings" "strings"
"time" "time"
"github.com/pkg/errors"
"restic/backend" "restic/backend"
"restic/debug" "restic/debug"
"github.com/juju/errors"
"github.com/pkg/sftp" "github.com/pkg/sftp"
) )
@ -40,7 +41,7 @@ func startClient(program string, args ...string) (*SFTP, error) {
// prefix the errors with the program name // prefix the errors with the program name
stderr, err := cmd.StderrPipe() stderr, err := cmd.StderrPipe()
if err != nil { if err != nil {
return nil, err return nil, errors.Wrap(err, "cmd.StderrPipe")
} }
go func() { go func() {
@ -56,16 +57,16 @@ func startClient(program string, args ...string) (*SFTP, error) {
// get stdin and stdout // get stdin and stdout
wr, err := cmd.StdinPipe() wr, err := cmd.StdinPipe()
if err != nil { if err != nil {
return nil, err return nil, errors.Wrap(err, "cmd.StdinPipe")
} }
rd, err := cmd.StdoutPipe() rd, err := cmd.StdoutPipe()
if err != nil { if err != nil {
return nil, err return nil, errors.Wrap(err, "cmd.StdoutPipe")
} }
// start the process // start the process
if err := cmd.Start(); err != nil { if err := cmd.Start(); err != nil {
return nil, err return nil, errors.Wrap(err, "cmd.Start")
} }
// wait in a different goroutine // wait in a different goroutine
@ -73,13 +74,13 @@ func startClient(program string, args ...string) (*SFTP, error) {
go func() { go func() {
err := cmd.Wait() err := cmd.Wait()
debug.Log("sftp.Wait", "ssh command exited, err %v", err) debug.Log("sftp.Wait", "ssh command exited, err %v", err)
ch <- err ch <- errors.Wrap(err, "cmd.Wait")
}() }()
// open the SFTP session // open the SFTP session
client, err := sftp.NewClientPipe(rd, wr) client, err := sftp.NewClientPipe(rd, wr)
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to start the sftp session, error: %v", err) return nil, errors.Errorf("unable to start the sftp session, error: %v", err)
} }
return &SFTP{c: client, cmd: cmd, result: ch}, nil return &SFTP{c: client, cmd: cmd, result: ch}, nil
@ -125,7 +126,7 @@ func Open(dir string, program string, args ...string) (*SFTP, error) {
// test if all necessary dirs and files are there // test if all necessary dirs and files are there
for _, d := range paths(dir) { for _, d := range paths(dir) {
if _, err := sftp.c.Lstat(d); err != nil { if _, err := sftp.c.Lstat(d); err != nil {
return nil, fmt.Errorf("%s does not exist", d) return nil, errors.Errorf("%s does not exist", d)
} }
} }
@ -181,7 +182,7 @@ func Create(dir string, program string, args ...string) (*SFTP, error) {
err = sftp.Close() err = sftp.Close()
if err != nil { if err != nil {
return nil, err return nil, errors.Wrap(err, "Close")
} }
// open backend // open backend
@ -206,9 +207,8 @@ func (r *SFTP) tempFile() (string, *sftp.File, error) {
buf := make([]byte, tempfileRandomSuffixLength) buf := make([]byte, tempfileRandomSuffixLength)
_, err := io.ReadFull(rand.Reader, buf) _, err := io.ReadFull(rand.Reader, buf)
if err != nil { if err != nil {
return "", nil, errors.Annotatef(err, return "", nil, errors.Errorf("unable to read %d random bytes for tempfile name: %v",
"unable to read %d random bytes for tempfile name", tempfileRandomSuffixLength, err)
tempfileRandomSuffixLength)
} }
// construct tempfile name // construct tempfile name
@ -217,7 +217,7 @@ func (r *SFTP) tempFile() (string, *sftp.File, error) {
// create file in temp dir // create file in temp dir
f, err := r.c.Create(name) f, err := r.c.Create(name)
if err != nil { if err != nil {
return "", nil, errors.Annotatef(err, "creating tempfile %q failed", name) return "", nil, errors.Errorf("creating tempfile %q failed: %v", name, err)
} }
return name, f, nil return name, f, nil
@ -231,7 +231,7 @@ func (r *SFTP) mkdirAll(dir string, mode os.FileMode) error {
return nil return nil
} }
return fmt.Errorf("mkdirAll(%s): entry exists but is not a directory", dir) return errors.Errorf("mkdirAll(%s): entry exists but is not a directory", dir)
} }
// create parent directories // create parent directories
@ -244,11 +244,11 @@ func (r *SFTP) mkdirAll(dir string, mode os.FileMode) error {
fi, err = r.c.Lstat(dir) fi, err = r.c.Lstat(dir)
if err != nil { if err != nil {
// return previous errors // return previous errors
return fmt.Errorf("mkdirAll(%s): unable to create directories: %v, %v", dir, errMkdirAll, errMkdir) return errors.Errorf("mkdirAll(%s): unable to create directories: %v, %v", dir, errMkdirAll, errMkdir)
} }
if !fi.IsDir() { if !fi.IsDir() {
return fmt.Errorf("mkdirAll(%s): entry exists but is not a directory", dir) return errors.Errorf("mkdirAll(%s): entry exists but is not a directory", dir)
} }
// set mode // set mode
@ -269,21 +269,22 @@ func (r *SFTP) renameFile(oldname string, t backend.Type, name string) error {
// test if new file exists // test if new file exists
if _, err := r.c.Lstat(filename); err == nil { if _, err := r.c.Lstat(filename); err == nil {
return fmt.Errorf("Close(): file %v already exists", filename) return errors.Errorf("Close(): file %v already exists", filename)
} }
err := r.c.Rename(oldname, filename) err := r.c.Rename(oldname, filename)
if err != nil { if err != nil {
return err return errors.Wrap(err, "Rename")
} }
// set mode to read-only // set mode to read-only
fi, err := r.c.Lstat(filename) fi, err := r.c.Lstat(filename)
if err != nil { if err != nil {
return err return errors.Wrap(err, "Lstat")
} }
return r.c.Chmod(filename, fi.Mode()&os.FileMode(^uint32(0222))) err = r.c.Chmod(filename, fi.Mode()&os.FileMode(^uint32(0222)))
return errors.Wrap(err, "Chmod")
} }
// Join joins the given paths and cleans them afterwards. This always uses // Join joins the given paths and cleans them afterwards. This always uses
@ -336,13 +337,13 @@ func (r *SFTP) Load(h backend.Handle, p []byte, off int64) (n int, err error) {
f, err := r.c.Open(r.filename(h.Type, h.Name)) f, err := r.c.Open(r.filename(h.Type, h.Name))
if err != nil { if err != nil {
return 0, err return 0, errors.Wrap(err, "Open")
} }
defer func() { defer func() {
e := f.Close() e := f.Close()
if err == nil && e != nil { if err == nil && e != nil {
err = e err = errors.Wrap(e, "Close")
} }
}() }()
@ -354,7 +355,7 @@ func (r *SFTP) Load(h backend.Handle, p []byte, off int64) (n int, err error) {
} }
if err != nil { if err != nil {
return 0, err return 0, errors.Wrap(err, "Seek")
} }
return io.ReadFull(f, p) return io.ReadFull(f, p)
@ -380,7 +381,7 @@ func (r *SFTP) Save(h backend.Handle, p []byte) (err error) {
n, err := tmpfile.Write(p) n, err := tmpfile.Write(p)
if err != nil { if err != nil {
return err return errors.Wrap(err, "Write")
} }
if n != len(p) { if n != len(p) {
@ -389,17 +390,13 @@ func (r *SFTP) Save(h backend.Handle, p []byte) (err error) {
err = tmpfile.Close() err = tmpfile.Close()
if err != nil { if err != nil {
return err return errors.Wrap(err, "Close")
} }
err = r.renameFile(filename, h.Type, h.Name) err = r.renameFile(filename, h.Type, h.Name)
debug.Log("sftp.Save", "save %v: rename %v: %v", debug.Log("sftp.Save", "save %v: rename %v: %v",
h, path.Base(filename), err) h, path.Base(filename), err)
if err != nil { return err
return fmt.Errorf("sftp: renameFile: %v", err)
}
return nil
} }
// Stat returns information about a blob. // Stat returns information about a blob.
@ -415,7 +412,7 @@ func (r *SFTP) Stat(h backend.Handle) (backend.BlobInfo, error) {
fi, err := r.c.Lstat(r.filename(h.Type, h.Name)) fi, err := r.c.Lstat(r.filename(h.Type, h.Name))
if err != nil { if err != nil {
return backend.BlobInfo{}, err return backend.BlobInfo{}, errors.Wrap(err, "Lstat")
} }
return backend.BlobInfo{Size: fi.Size()}, nil return backend.BlobInfo{Size: fi.Size()}, nil
@ -429,12 +426,12 @@ func (r *SFTP) Test(t backend.Type, name string) (bool, error) {
} }
_, err := r.c.Lstat(r.filename(t, name)) _, err := r.c.Lstat(r.filename(t, name))
if os.IsNotExist(err) { if os.IsNotExist(errors.Cause(err)) {
return false, nil return false, nil
} }
if err != nil { if err != nil {
return false, err return false, errors.Wrap(err, "Lstat")
} }
return true, nil return true, nil

View File

@ -6,6 +6,8 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/pkg/errors"
"restic/backend" "restic/backend"
"restic/backend/sftp" "restic/backend/sftp"
"restic/backend/test" "restic/backend/test"
@ -37,7 +39,7 @@ func init() {
for _, dir := range strings.Split(TestSFTPPath, ":") { for _, dir := range strings.Split(TestSFTPPath, ":") {
testpath := filepath.Join(dir, "sftp-server") testpath := filepath.Join(dir, "sftp-server")
_, err := os.Stat(testpath) _, err := os.Stat(testpath)
if !os.IsNotExist(err) { if !os.IsNotExist(errors.Cause(err)) {
sftpserver = testpath sftpserver = testpath
break break
} }

View File

@ -10,6 +10,8 @@ import (
"sort" "sort"
"testing" "testing"
"github.com/pkg/errors"
"restic/backend" "restic/backend"
. "restic/test" . "restic/test"
) )
@ -223,7 +225,7 @@ func TestLoad(t testing.TB) {
// if we requested data beyond the end of the file, require // if we requested data beyond the end of the file, require
// ErrUnexpectedEOF error // ErrUnexpectedEOF error
if l > len(d) { if l > len(d) {
if err != io.ErrUnexpectedEOF { if errors.Cause(err) != io.ErrUnexpectedEOF {
t.Errorf("Load(%d, %d) did not return io.ErrUnexpectedEOF", len(buf), int64(o)) t.Errorf("Load(%d, %d) did not return io.ErrUnexpectedEOF", len(buf), int64(o))
} }
err = nil err = nil
@ -270,7 +272,7 @@ func TestLoad(t testing.TB) {
// if we requested data beyond the end of the file, require // if we requested data beyond the end of the file, require
// ErrUnexpectedEOF error // ErrUnexpectedEOF error
if l > len(d) { if l > len(d) {
if err != io.ErrUnexpectedEOF { if errors.Cause(err) != io.ErrUnexpectedEOF {
t.Errorf("Load(%d, %d) did not return io.ErrUnexpectedEOF", len(buf), int64(o)) t.Errorf("Load(%d, %d) did not return io.ErrUnexpectedEOF", len(buf), int64(o))
continue continue
} }
@ -303,7 +305,7 @@ func TestLoad(t testing.TB) {
t.Errorf("wrong length for larger buffer returned, want %d, got %d", length, n) t.Errorf("wrong length for larger buffer returned, want %d, got %d", length, n)
} }
if err != io.ErrUnexpectedEOF { if errors.Cause(err) != io.ErrUnexpectedEOF {
t.Errorf("wrong error returned for larger buffer: want io.ErrUnexpectedEOF, got %#v", err) t.Errorf("wrong error returned for larger buffer: want io.ErrUnexpectedEOF, got %#v", err)
} }
@ -337,7 +339,7 @@ func TestLoadNegativeOffset(t testing.TB) {
// if we requested data beyond the end of the file, require // if we requested data beyond the end of the file, require
// ErrUnexpectedEOF error // ErrUnexpectedEOF error
if len(buf) > -o { if len(buf) > -o {
if err != io.ErrUnexpectedEOF { if errors.Cause(err) != io.ErrUnexpectedEOF {
t.Errorf("Load(%d, %d) did not return io.ErrUnexpectedEOF", len(buf), o) t.Errorf("Load(%d, %d) did not return io.ErrUnexpectedEOF", len(buf), o)
continue continue
} }

View File

@ -1,7 +1,7 @@
package test_test package test_test
import ( import (
"errors" "github.com/pkg/errors"
"restic/backend" "restic/backend"
"restic/backend/mem" "restic/backend/mem"

View File

@ -1,6 +1,10 @@
package backend package backend
import "io" import (
"io"
"github.com/pkg/errors"
)
// LoadAll reads all data stored in the backend for the handle. The buffer buf // LoadAll reads all data stored in the backend for the handle. The buffer buf
// is resized to accomodate all data in the blob. Errors returned by be.Load() // is resized to accomodate all data in the blob. Errors returned by be.Load()
@ -9,7 +13,7 @@ import "io"
func LoadAll(be Backend, h Handle, buf []byte) ([]byte, error) { func LoadAll(be Backend, h Handle, buf []byte) ([]byte, error) {
fi, err := be.Stat(h) fi, err := be.Stat(h)
if err != nil { if err != nil {
return nil, err return nil, errors.Wrap(err, "Stat")
} }
if fi.Size > int64(len(buf)) { if fi.Size > int64(len(buf)) {
@ -17,7 +21,7 @@ func LoadAll(be Backend, h Handle, buf []byte) ([]byte, error) {
} }
n, err := be.Load(h, buf, 0) n, err := be.Load(h, buf, 0)
if err == io.ErrUnexpectedEOF { if errors.Cause(err) == io.ErrUnexpectedEOF {
err = nil err = nil
} }
buf = buf[:n] buf = buf[:n]

View File

@ -1,14 +1,14 @@
package restic package restic
import ( import (
"errors"
"fmt"
"io" "io"
"os" "os"
"path/filepath" "path/filepath"
"runtime" "runtime"
"strings" "strings"
"github.com/pkg/errors"
"restic/backend" "restic/backend"
"restic/debug" "restic/debug"
"restic/fs" "restic/fs"
@ -48,13 +48,13 @@ func (c *Cache) Has(t backend.Type, subtype string, id backend.ID) (bool, error)
defer fd.Close() defer fd.Close()
if err != nil { if err != nil {
if os.IsNotExist(err) { if os.IsNotExist(errors.Cause(err)) {
debug.Log("Cache.Has", "test for file %v: not cached", filename) debug.Log("Cache.Has", "test for file %v: not cached", filename)
return false, nil return false, nil
} }
debug.Log("Cache.Has", "test for file %v: error %v", filename, err) debug.Log("Cache.Has", "test for file %v: error %v", filename, err)
return false, err return false, errors.Wrap(err, "Open")
} }
debug.Log("Cache.Has", "test for file %v: is cached", filename) debug.Log("Cache.Has", "test for file %v: is cached", filename)
@ -73,13 +73,13 @@ func (c *Cache) Store(t backend.Type, subtype string, id backend.ID) (io.WriteCl
dirname := filepath.Dir(filename) dirname := filepath.Dir(filename)
err = fs.MkdirAll(dirname, 0700) err = fs.MkdirAll(dirname, 0700)
if err != nil { if err != nil {
return nil, err return nil, errors.Wrap(err, "MkdirAll")
} }
file, err := fs.Create(filename) file, err := fs.Create(filename)
if err != nil { if err != nil {
debug.Log("Cache.Store", "error creating file %v: %v", filename, err) debug.Log("Cache.Store", "error creating file %v: %v", filename, err)
return nil, err return nil, errors.Wrap(err, "Create")
} }
debug.Log("Cache.Store", "created file %v", filename) debug.Log("Cache.Store", "created file %v", filename)
@ -106,11 +106,11 @@ func (c *Cache) purge(t backend.Type, subtype string, id backend.ID) error {
err = fs.Remove(filename) err = fs.Remove(filename)
debug.Log("Cache.purge", "Remove file %v: %v", filename, err) debug.Log("Cache.purge", "Remove file %v: %v", filename, err)
if err != nil && os.IsNotExist(err) { if err != nil && os.IsNotExist(errors.Cause(err)) {
return nil return nil
} }
return err return errors.Wrap(err, "Remove")
} }
// Clear removes information from the cache that isn't present in the repository any more. // Clear removes information from the cache that isn't present in the repository any more.
@ -155,21 +155,21 @@ func (c *Cache) list(t backend.Type) ([]cacheEntry, error) {
case backend.Snapshot: case backend.Snapshot:
dir = filepath.Join(c.base, "snapshots") dir = filepath.Join(c.base, "snapshots")
default: default:
return nil, fmt.Errorf("cache not supported for type %v", t) return nil, errors.Errorf("cache not supported for type %v", t)
} }
fd, err := fs.Open(dir) fd, err := fs.Open(dir)
if err != nil { if err != nil {
if os.IsNotExist(err) { if os.IsNotExist(errors.Cause(err)) {
return []cacheEntry{}, nil return []cacheEntry{}, nil
} }
return nil, err return nil, errors.Wrap(err, "Open")
} }
defer fd.Close() defer fd.Close()
fis, err := fd.Readdir(-1) fis, err := fd.Readdir(-1)
if err != nil { if err != nil {
return nil, err return nil, errors.Wrap(err, "Readdir")
} }
entries := make([]cacheEntry, 0, len(fis)) entries := make([]cacheEntry, 0, len(fis))
@ -207,7 +207,7 @@ func (c *Cache) filename(t backend.Type, subtype string, id backend.ID) (string,
return filepath.Join(c.base, "snapshots", filename), nil return filepath.Join(c.base, "snapshots", filename), nil
} }
return "", fmt.Errorf("cache not supported for type %v", t) return "", errors.Errorf("cache not supported for type %v", t)
} }
func getCacheDir() (string, error) { func getCacheDir() (string, error) {
@ -231,21 +231,21 @@ func getWindowsCacheDir() (string, error) {
cachedir = filepath.Join(cachedir, "restic") cachedir = filepath.Join(cachedir, "restic")
fi, err := fs.Stat(cachedir) fi, err := fs.Stat(cachedir)
if os.IsNotExist(err) { if os.IsNotExist(errors.Cause(err)) {
err = fs.MkdirAll(cachedir, 0700) err = fs.MkdirAll(cachedir, 0700)
if err != nil { if err != nil {
return "", err return "", errors.Wrap(err, "MkdirAll")
} }
return cachedir, nil return cachedir, nil
} }
if err != nil { if err != nil {
return "", err return "", errors.Wrap(err, "Stat")
} }
if !fi.IsDir() { if !fi.IsDir() {
return "", fmt.Errorf("cache dir %v is not a directory", cachedir) return "", errors.Errorf("cache dir %v is not a directory", cachedir)
} }
return cachedir, nil return cachedir, nil
} }
@ -268,10 +268,10 @@ func getXDGCacheDir() (string, error) {
} }
fi, err := fs.Stat(cachedir) fi, err := fs.Stat(cachedir)
if os.IsNotExist(err) { if os.IsNotExist(errors.Cause(err)) {
err = fs.MkdirAll(cachedir, 0700) err = fs.MkdirAll(cachedir, 0700)
if err != nil { if err != nil {
return "", err return "", errors.Wrap(err, "MkdirAll")
} }
fi, err = fs.Stat(cachedir) fi, err = fs.Stat(cachedir)
@ -279,11 +279,11 @@ func getXDGCacheDir() (string, error) {
} }
if err != nil { if err != nil {
return "", err return "", errors.Wrap(err, "Stat")
} }
if !fi.IsDir() { if !fi.IsDir() {
return "", fmt.Errorf("cache dir %v is not a directory", cachedir) return "", errors.Errorf("cache dir %v is not a directory", cachedir)
} }
return cachedir, nil return cachedir, nil

View File

@ -18,6 +18,9 @@ func TestCache(t *testing.T) {
// archive some files, this should automatically cache all blobs from the snapshot // archive some files, this should automatically cache all blobs from the snapshot
_, _, err = arch.Snapshot(nil, []string{BenchArchiveDirectory}, nil) _, _, err = arch.Snapshot(nil, []string{BenchArchiveDirectory}, nil)
if err != nil {
t.Fatal(err)
}
// TODO: test caching index // TODO: test caching index
} }

View File

@ -2,10 +2,11 @@ package checker
import ( import (
"bytes" "bytes"
"errors"
"fmt" "fmt"
"sync" "sync"
"github.com/pkg/errors"
"restic" "restic"
"restic/backend" "restic/backend"
"restic/crypto" "restic/crypto"
@ -84,7 +85,7 @@ func (c *Checker) LoadIndex() (hints []error, errs []error) {
worker := func(id backend.ID, done <-chan struct{}) error { worker := func(id backend.ID, done <-chan struct{}) error {
debug.Log("LoadIndex", "worker got index %v", id) debug.Log("LoadIndex", "worker got index %v", id)
idx, err := repository.LoadIndexWithDecoder(c.repo, id, repository.DecodeIndex) idx, err := repository.LoadIndexWithDecoder(c.repo, id, repository.DecodeIndex)
if err == repository.ErrOldIndexFormat { if errors.Cause(err) == repository.ErrOldIndexFormat {
debug.Log("LoadIndex", "index %v has old format", id.Str()) debug.Log("LoadIndex", "index %v has old format", id.Str())
hints = append(hints, ErrOldIndexFormat{id}) hints = append(hints, ErrOldIndexFormat{id})
@ -126,7 +127,7 @@ func (c *Checker) LoadIndex() (hints []error, errs []error) {
debug.Log("LoadIndex", "process index %v", res.ID) debug.Log("LoadIndex", "process index %v", res.ID)
idxID, err := backend.ParseID(res.ID) idxID, err := backend.ParseID(res.ID)
if err != nil { if err != nil {
errs = append(errs, fmt.Errorf("unable to parse as index ID: %v", res.ID)) errs = append(errs, errors.Errorf("unable to parse as index ID: %v", res.ID))
continue continue
} }
@ -281,7 +282,7 @@ func loadTreeFromSnapshot(repo *repository.Repository, id backend.ID) (backend.I
if sn.Tree == nil { if sn.Tree == nil {
debug.Log("Checker.loadTreeFromSnapshot", "snapshot %v has no tree", id.Str()) debug.Log("Checker.loadTreeFromSnapshot", "snapshot %v has no tree", id.Str())
return backend.ID{}, fmt.Errorf("snapshot %v has no tree", id) return backend.ID{}, errors.Errorf("snapshot %v has no tree", id)
} }
return *sn.Tree, nil return *sn.Tree, nil
@ -583,24 +584,24 @@ func (c *Checker) checkTree(id backend.ID, tree *restic.Tree) (errs []error) {
switch node.Type { switch node.Type {
case "file": case "file":
if node.Content == nil { if node.Content == nil {
errs = append(errs, Error{TreeID: id, Err: fmt.Errorf("file %q has nil blob list", node.Name)}) errs = append(errs, Error{TreeID: id, Err: errors.Errorf("file %q has nil blob list", node.Name)})
} }
for b, blobID := range node.Content { for b, blobID := range node.Content {
if blobID.IsNull() { if blobID.IsNull() {
errs = append(errs, Error{TreeID: id, Err: fmt.Errorf("file %q blob %d has null ID", node.Name, b)}) errs = append(errs, Error{TreeID: id, Err: errors.Errorf("file %q blob %d has null ID", node.Name, b)})
continue continue
} }
blobs = append(blobs, blobID) blobs = append(blobs, blobID)
} }
case "dir": case "dir":
if node.Subtree == nil { if node.Subtree == nil {
errs = append(errs, Error{TreeID: id, Err: fmt.Errorf("dir node %q has no subtree", node.Name)}) errs = append(errs, Error{TreeID: id, Err: errors.Errorf("dir node %q has no subtree", node.Name)})
continue continue
} }
if node.Subtree.IsNull() { if node.Subtree.IsNull() {
errs = append(errs, Error{TreeID: id, Err: fmt.Errorf("dir node %q subtree id is null", node.Name)}) errs = append(errs, Error{TreeID: id, Err: errors.Errorf("dir node %q subtree id is null", node.Name)})
continue continue
} }
@ -608,7 +609,7 @@ func (c *Checker) checkTree(id backend.ID, tree *restic.Tree) (errs []error) {
// nothing to check // nothing to check
default: default:
errs = append(errs, Error{TreeID: id, Err: fmt.Errorf("node %q with invalid type %q", node.Name, node.Type)}) errs = append(errs, Error{TreeID: id, Err: errors.Errorf("node %q with invalid type %q", node.Name, node.Type)})
} }
if node.Name == "" { if node.Name == "" {
@ -670,7 +671,7 @@ func checkPack(r *repository.Repository, id backend.ID) error {
hash := backend.Hash(buf) hash := backend.Hash(buf)
if !hash.Equal(id) { if !hash.Equal(id) {
debug.Log("Checker.checkPack", "Pack ID does not match, want %v, got %v", id.Str(), hash.Str()) debug.Log("Checker.checkPack", "Pack ID does not match, want %v, got %v", id.Str(), hash.Str())
return fmt.Errorf("Pack ID does not match, want %v, got %v", id.Str(), hash.Str()) return errors.Errorf("Pack ID does not match, want %v, got %v", id.Str(), hash.Str())
} }
blobs, err := pack.List(r.Key(), bytes.NewReader(buf), int64(len(buf))) blobs, err := pack.List(r.Key(), bytes.NewReader(buf), int64(len(buf)))
@ -686,20 +687,20 @@ func checkPack(r *repository.Repository, id backend.ID) error {
plainBuf, err = crypto.Decrypt(r.Key(), plainBuf, buf[blob.Offset:blob.Offset+blob.Length]) plainBuf, err = crypto.Decrypt(r.Key(), plainBuf, buf[blob.Offset:blob.Offset+blob.Length])
if err != nil { if err != nil {
debug.Log("Checker.checkPack", " error decrypting blob %v: %v", blob.ID.Str(), err) debug.Log("Checker.checkPack", " error decrypting blob %v: %v", blob.ID.Str(), err)
errs = append(errs, fmt.Errorf("blob %v: %v", i, err)) errs = append(errs, errors.Errorf("blob %v: %v", i, err))
continue continue
} }
hash := backend.Hash(plainBuf) hash := backend.Hash(plainBuf)
if !hash.Equal(blob.ID) { if !hash.Equal(blob.ID) {
debug.Log("Checker.checkPack", " Blob ID does not match, want %v, got %v", blob.ID.Str(), hash.Str()) debug.Log("Checker.checkPack", " Blob ID does not match, want %v, got %v", blob.ID.Str(), hash.Str())
errs = append(errs, fmt.Errorf("Blob ID does not match, want %v, got %v", blob.ID.Str(), hash.Str())) errs = append(errs, errors.Errorf("Blob ID does not match, want %v, got %v", blob.ID.Str(), hash.Str()))
continue continue
} }
} }
if len(errs) > 0 { if len(errs) > 0 {
return fmt.Errorf("pack %v contains %v errors: %v", id.Str(), len(errs), errs) return errors.Errorf("pack %v contains %v errors: %v", id.Str(), len(errs), errs)
} }
return nil return nil

View File

@ -5,9 +5,10 @@ import (
"crypto/cipher" "crypto/cipher"
"crypto/rand" "crypto/rand"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"github.com/pkg/errors"
"golang.org/x/crypto/poly1305" "golang.org/x/crypto/poly1305"
) )
@ -167,7 +168,7 @@ func (m *MACKey) UnmarshalJSON(data []byte) error {
j := jsonMACKey{} j := jsonMACKey{}
err := json.Unmarshal(data, &j) err := json.Unmarshal(data, &j)
if err != nil { if err != nil {
return err return errors.Wrap(err, "Unmarshal")
} }
copy(m.K[:], j.K) copy(m.K[:], j.K)
copy(m.R[:], j.R) copy(m.R[:], j.R)
@ -205,7 +206,7 @@ func (k *EncryptionKey) UnmarshalJSON(data []byte) error {
d := make([]byte, aesKeySize) d := make([]byte, aesKeySize)
err := json.Unmarshal(data, &d) err := json.Unmarshal(data, &d)
if err != nil { if err != nil {
return err return errors.Wrap(err, "Unmarshal")
} }
copy(k[:], d) copy(k[:], d)

View File

@ -2,10 +2,10 @@ package crypto
import ( import (
"crypto/rand" "crypto/rand"
"fmt"
"time" "time"
sscrypt "github.com/elithrar/simple-scrypt" sscrypt "github.com/elithrar/simple-scrypt"
"github.com/pkg/errors"
"golang.org/x/crypto/scrypt" "golang.org/x/crypto/scrypt"
) )
@ -37,7 +37,7 @@ func Calibrate(timeout time.Duration, memory int) (KDFParams, error) {
params, err := sscrypt.Calibrate(timeout, memory, defaultParams) params, err := sscrypt.Calibrate(timeout, memory, defaultParams)
if err != nil { if err != nil {
return DefaultKDFParams, err return DefaultKDFParams, errors.Wrap(err, "scrypt.Calibrate")
} }
return KDFParams{ return KDFParams{
@ -51,7 +51,7 @@ func Calibrate(timeout time.Duration, memory int) (KDFParams, error) {
// using the supplied parameters N, R and P and the Salt. // using the supplied parameters N, R and P and the Salt.
func KDF(p KDFParams, salt []byte, password string) (*Key, error) { func KDF(p KDFParams, salt []byte, password string) (*Key, error) {
if len(salt) != saltLength { if len(salt) != saltLength {
return nil, fmt.Errorf("scrypt() called with invalid salt bytes (len %d)", len(salt)) return nil, errors.Errorf("scrypt() called with invalid salt bytes (len %d)", len(salt))
} }
// make sure we have valid parameters // make sure we have valid parameters
@ -64,7 +64,7 @@ func KDF(p KDFParams, salt []byte, password string) (*Key, error) {
} }
if err := params.Check(); err != nil { if err := params.Check(); err != nil {
return nil, err return nil, errors.Wrap(err, "Check")
} }
derKeys := &Key{} derKeys := &Key{}
@ -72,11 +72,11 @@ func KDF(p KDFParams, salt []byte, password string) (*Key, error) {
keybytes := macKeySize + aesKeySize keybytes := macKeySize + aesKeySize
scryptKeys, err := scrypt.Key([]byte(password), salt, p.N, p.R, p.P, keybytes) scryptKeys, err := scrypt.Key([]byte(password), salt, p.N, p.R, p.P, keybytes)
if err != nil { if err != nil {
return nil, fmt.Errorf("error deriving keys from password: %v", err) return nil, errors.Wrap(err, "scrypt.Key")
} }
if len(scryptKeys) != keybytes { if len(scryptKeys) != keybytes {
return nil, fmt.Errorf("invalid numbers of bytes expanded from scrypt(): %d", len(scryptKeys)) return nil, errors.Errorf("invalid numbers of bytes expanded from scrypt(): %d", len(scryptKeys))
} }
// first 32 byte of scrypt output is the encryption key // first 32 byte of scrypt output is the encryption key

View File

@ -14,6 +14,8 @@ import (
"strings" "strings"
"sync" "sync"
"time" "time"
"github.com/pkg/errors"
) )
type process struct { type process struct {
@ -59,7 +61,7 @@ func initDebugLogger() {
} }
} }
if err != nil && os.IsNotExist(err) { if err != nil && os.IsNotExist(errors.Cause(err)) {
f, err = fs.OpenFile(debugfile, os.O_WRONLY|os.O_CREATE, 0600) f, err = fs.OpenFile(debugfile, os.O_WRONLY|os.O_CREATE, 0600)
} }

38
src/restic/errors.go Normal file
View File

@ -0,0 +1,38 @@
package restic
import "fmt"
// fatalError is an error that should be printed to the user, then the program
// should exit with an error code.
type fatalError string
func (e fatalError) Error() string {
return string(e)
}
func (e fatalError) Fatal() bool {
return true
}
// Fataler is an error which should be printed to the user directly.
// Afterwards, the program should exit with an error.
type Fataler interface {
Fatal() bool
}
// IsFatal returns true if err is a fatal message that should be printed to the
// user. Then, the program should exit.
func IsFatal(err error) bool {
e, ok := err.(Fataler)
return ok && e.Fatal()
}
// Fatal returns an error which implements the Fataler interface.
func Fatal(s string) error {
return fatalError(s)
}
// Fatalf returns an error which implements the Fataler interface.
func Fatalf(s string, data ...interface{}) error {
return fatalError(fmt.Sprintf(s, data...))
}

View File

@ -1,9 +1,10 @@
package filter package filter
import ( import (
"errors"
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/pkg/errors"
) )
// ErrBadString is returned when Match is called with the empty string as the // ErrBadString is returned when Match is called with the empty string as the
@ -84,7 +85,7 @@ func match(patterns, strs []string) (matched bool, err error) {
for i := len(patterns) - 1; i >= 0; i-- { for i := len(patterns) - 1; i >= 0; i-- {
ok, err := filepath.Match(patterns[i], strs[offset+i]) ok, err := filepath.Match(patterns[i], strs[offset+i])
if err != nil { if err != nil {
return false, err return false, errors.Wrap(err, "Match")
} }
if !ok { if !ok {

View File

@ -6,13 +6,15 @@ import (
"os" "os"
"syscall" "syscall"
"github.com/pkg/errors"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
) )
// Open opens a file for reading, without updating the atime and without caching data on read. // Open opens a file for reading, without updating the atime and without caching data on read.
func Open(name string) (File, error) { func Open(name string) (File, error) {
file, err := os.OpenFile(name, os.O_RDONLY|syscall.O_NOATIME, 0) file, err := os.OpenFile(name, os.O_RDONLY|syscall.O_NOATIME, 0)
if os.IsPermission(err) { if os.IsPermission(errors.Cause(err)) {
file, err = os.OpenFile(name, os.O_RDONLY, 0) file, err = os.OpenFile(name, os.O_RDONLY, 0)
} }
return &nonCachingFile{File: file}, err return &nonCachingFile{File: file}, err

View File

@ -4,9 +4,10 @@
package fuse package fuse
import ( import (
"errors"
"sync" "sync"
"github.com/pkg/errors"
"restic" "restic"
"restic/backend" "restic/backend"
"restic/debug" "restic/debug"

View File

@ -5,11 +5,12 @@ package fuse
import ( import (
"bytes" "bytes"
"errors"
"math/rand" "math/rand"
"testing" "testing"
"time" "time"
"github.com/pkg/errors"
"bazil.org/fuse" "bazil.org/fuse"
"restic" "restic"

View File

@ -4,11 +4,12 @@
package fuse package fuse
import ( import (
"restic"
"restic/repository"
"bazil.org/fuse" "bazil.org/fuse"
"bazil.org/fuse/fs" "bazil.org/fuse/fs"
"golang.org/x/net/context" "golang.org/x/net/context"
"restic"
"restic/repository"
) )
// Statically ensure that *file implements the given interface // Statically ensure that *file implements the given interface

View File

@ -2,7 +2,6 @@
package index package index
import ( import (
"errors"
"fmt" "fmt"
"os" "os"
"restic" "restic"
@ -12,6 +11,8 @@ import (
"restic/pack" "restic/pack"
"restic/types" "restic/types"
"restic/worker" "restic/worker"
"github.com/pkg/errors"
) )
// Pack contains information about the contents of a pack. // Pack contains information about the contents of a pack.
@ -180,7 +181,7 @@ func Load(repo types.Repository, p *restic.Progress) (*Index, error) {
// error is returned. // error is returned.
func (idx *Index) AddPack(id backend.ID, size int64, entries []pack.Blob) error { func (idx *Index) AddPack(id backend.ID, size int64, entries []pack.Blob) error {
if _, ok := idx.Packs[id]; ok { if _, ok := idx.Packs[id]; ok {
return fmt.Errorf("pack %v already present in the index", id.Str()) return errors.Errorf("pack %v already present in the index", id.Str())
} }
idx.Packs[id] = Pack{Size: size, Entries: entries} idx.Packs[id] = Pack{Size: size, Entries: entries}
@ -203,7 +204,7 @@ func (idx *Index) AddPack(id backend.ID, size int64, entries []pack.Blob) error
// RemovePack deletes a pack from the index. // RemovePack deletes a pack from the index.
func (idx *Index) RemovePack(id backend.ID) error { func (idx *Index) RemovePack(id backend.ID) error {
if _, ok := idx.Packs[id]; !ok { if _, ok := idx.Packs[id]; !ok {
return fmt.Errorf("pack %v not found in the index", id.Str()) return errors.Errorf("pack %v not found in the index", id.Str())
} }
for _, blob := range idx.Packs[id].Entries { for _, blob := range idx.Packs[id].Entries {
@ -278,7 +279,7 @@ func (idx *Index) FindBlob(h pack.Handle) ([]Location, error) {
for packID := range blob.Packs { for packID := range blob.Packs {
pack, ok := idx.Packs[packID] pack, ok := idx.Packs[packID]
if !ok { if !ok {
return nil, fmt.Errorf("pack %v not found in index", packID.Str()) return nil, errors.Errorf("pack %v not found in index", packID.Str())
} }
for _, entry := range pack.Entries { for _, entry := range pack.Entries {

View File

@ -9,6 +9,8 @@ import (
"syscall" "syscall"
"time" "time"
"github.com/pkg/errors"
"restic/backend" "restic/backend"
"restic/debug" "restic/debug"
"restic/repository" "restic/repository"
@ -47,7 +49,7 @@ func (e ErrAlreadyLocked) Error() string {
// IsAlreadyLocked returns true iff err is an instance of ErrAlreadyLocked. // IsAlreadyLocked returns true iff err is an instance of ErrAlreadyLocked.
func IsAlreadyLocked(err error) bool { func IsAlreadyLocked(err error) bool {
if _, ok := err.(ErrAlreadyLocked); ok { if _, ok := errors.Cause(err).(ErrAlreadyLocked); ok {
return true return true
} }
@ -189,7 +191,7 @@ var staleTimeout = 30 * time.Minute
// process isn't alive any more. // process isn't alive any more.
func (l *Lock) Stale() bool { func (l *Lock) Stale() bool {
debug.Log("Lock.Stale", "testing if lock %v for process %d is stale", l, l.PID) debug.Log("Lock.Stale", "testing if lock %v for process %d is stale", l, l.PID)
if time.Now().Sub(l.Time) > staleTimeout { if time.Since(l.Time) > staleTimeout {
debug.Log("Lock.Stale", "lock is stale, timestamp is too old: %v\n", l.Time) debug.Log("Lock.Stale", "lock is stale, timestamp is too old: %v\n", l.Time)
return true return true
} }

View File

@ -8,6 +8,8 @@ import (
"strconv" "strconv"
"syscall" "syscall"
"github.com/pkg/errors"
"restic/debug" "restic/debug"
) )
@ -16,11 +18,11 @@ func uidGidInt(u user.User) (uid, gid uint32, err error) {
var ui, gi int64 var ui, gi int64
ui, err = strconv.ParseInt(u.Uid, 10, 32) ui, err = strconv.ParseInt(u.Uid, 10, 32)
if err != nil { if err != nil {
return return uid, gid, errors.Wrap(err, "ParseInt")
} }
gi, err = strconv.ParseInt(u.Gid, 10, 32) gi, err = strconv.ParseInt(u.Gid, 10, 32)
if err != nil { if err != nil {
return return uid, gid, errors.Wrap(err, "ParseInt")
} }
uid = uint32(ui) uid = uint32(ui)
gid = uint32(gi) gid = uint32(gi)

View File

@ -10,6 +10,8 @@ import (
"syscall" "syscall"
"time" "time"
"github.com/pkg/errors"
"runtime" "runtime"
"restic/backend" "restic/backend"
@ -17,8 +19,6 @@ import (
"restic/fs" "restic/fs"
"restic/pack" "restic/pack"
"restic/repository" "restic/repository"
"github.com/juju/errors"
) )
// Node is a file, directory or other item in a backup. // Node is a file, directory or other item in a backup.
@ -114,32 +114,32 @@ func (node *Node) CreateAt(path string, repo *repository.Repository) error {
switch node.Type { switch node.Type {
case "dir": case "dir":
if err := node.createDirAt(path); err != nil { if err := node.createDirAt(path); err != nil {
return errors.Annotate(err, "createDirAt") return err
} }
case "file": case "file":
if err := node.createFileAt(path, repo); err != nil { if err := node.createFileAt(path, repo); err != nil {
return errors.Annotate(err, "createFileAt") return err
} }
case "symlink": case "symlink":
if err := node.createSymlinkAt(path); err != nil { if err := node.createSymlinkAt(path); err != nil {
return errors.Annotate(err, "createSymlinkAt") return err
} }
case "dev": case "dev":
if err := node.createDevAt(path); err != nil { if err := node.createDevAt(path); err != nil {
return errors.Annotate(err, "createDevAt") return err
} }
case "chardev": case "chardev":
if err := node.createCharDevAt(path); err != nil { if err := node.createCharDevAt(path); err != nil {
return errors.Annotate(err, "createCharDevAt") return err
} }
case "fifo": case "fifo":
if err := node.createFifoAt(path); err != nil { if err := node.createFifoAt(path); err != nil {
return errors.Annotate(err, "createFifoAt") return err
} }
case "socket": case "socket":
return nil return nil
default: default:
return fmt.Errorf("filetype %q not implemented!\n", node.Type) return errors.Errorf("filetype %q not implemented!\n", node.Type)
} }
err := node.restoreMetadata(path) err := node.restoreMetadata(path)
@ -155,13 +155,13 @@ func (node Node) restoreMetadata(path string) error {
err = lchown(path, int(node.UID), int(node.GID)) err = lchown(path, int(node.UID), int(node.GID))
if err != nil { if err != nil {
return errors.Annotate(err, "Lchown") return errors.Wrap(err, "Lchown")
} }
if node.Type != "symlink" { if node.Type != "symlink" {
err = fs.Chmod(path, node.Mode) err = fs.Chmod(path, node.Mode)
if err != nil { if err != nil {
return errors.Annotate(err, "Chmod") return errors.Wrap(err, "Chmod")
} }
} }
@ -183,15 +183,11 @@ func (node Node) RestoreTimestamps(path string) error {
} }
if node.Type == "symlink" { if node.Type == "symlink" {
if err := node.restoreSymlinkTimestamps(path, utimes); err != nil { return node.restoreSymlinkTimestamps(path, utimes)
return err
}
return nil
} }
if err := syscall.UtimesNano(path, utimes[:]); err != nil { if err := syscall.UtimesNano(path, utimes[:]); err != nil {
return errors.Annotate(err, "UtimesNano") return errors.Wrap(err, "UtimesNano")
} }
return nil return nil
@ -200,7 +196,7 @@ func (node Node) RestoreTimestamps(path string) error {
func (node Node) createDirAt(path string) error { func (node Node) createDirAt(path string) error {
err := fs.Mkdir(path, node.Mode) err := fs.Mkdir(path, node.Mode)
if err != nil { if err != nil {
return errors.Annotate(err, "Mkdir") return errors.Wrap(err, "Mkdir")
} }
return nil return nil
@ -211,7 +207,7 @@ func (node Node) createFileAt(path string, repo *repository.Repository) error {
defer f.Close() defer f.Close()
if err != nil { if err != nil {
return errors.Annotate(err, "OpenFile") return errors.Wrap(err, "OpenFile")
} }
var buf []byte var buf []byte
@ -228,12 +224,12 @@ func (node Node) createFileAt(path string, repo *repository.Repository) error {
buf, err := repo.LoadBlob(id, pack.Data, buf) buf, err := repo.LoadBlob(id, pack.Data, buf)
if err != nil { if err != nil {
return errors.Annotate(err, "Load") return err
} }
_, err = f.Write(buf) _, err = f.Write(buf)
if err != nil { if err != nil {
return errors.Annotate(err, "Write") return errors.Wrap(err, "Write")
} }
} }
@ -247,7 +243,7 @@ func (node Node) createSymlinkAt(path string) error {
} }
err := fs.Symlink(node.LinkTarget, path) err := fs.Symlink(node.LinkTarget, path)
if err != nil { if err != nil {
return errors.Annotate(err, "Symlink") return errors.Wrap(err, "Symlink")
} }
return nil return nil
@ -280,11 +276,11 @@ func (node *Node) UnmarshalJSON(data []byte) error {
err := json.Unmarshal(data, nj) err := json.Unmarshal(data, nj)
if err != nil { if err != nil {
return err return errors.Wrap(err, "Unmarshal")
} }
nj.Name, err = strconv.Unquote(`"` + nj.Name + `"`) nj.Name, err = strconv.Unquote(`"` + nj.Name + `"`)
return err return errors.Wrap(err, "Unquote")
} }
func (node Node) Equals(other Node) bool { func (node Node) Equals(other Node) bool {
@ -422,7 +418,7 @@ func (node *Node) fillUser(stat statT) error {
username, err := lookupUsername(strconv.Itoa(int(stat.uid()))) username, err := lookupUsername(strconv.Itoa(int(stat.uid())))
if err != nil { if err != nil {
return errors.Annotate(err, "fillUser") return err
} }
node.User = username node.User = username
@ -470,7 +466,7 @@ func (node *Node) fillExtra(path string, fi os.FileInfo) error {
var err error var err error
if err = node.fillUser(stat); err != nil { if err = node.fillUser(stat); err != nil {
return errors.Annotate(err, "fillExtra") return err
} }
switch node.Type { switch node.Type {
@ -480,6 +476,7 @@ func (node *Node) fillExtra(path string, fi os.FileInfo) error {
case "dir": case "dir":
case "symlink": case "symlink":
node.LinkTarget, err = fs.Readlink(path) node.LinkTarget, err = fs.Readlink(path)
err = errors.Wrap(err, "Readlink")
case "dev": case "dev":
node.Device = uint64(stat.rdev()) node.Device = uint64(stat.rdev())
case "chardev": case "chardev":
@ -487,7 +484,7 @@ func (node *Node) fillExtra(path string, fi os.FileInfo) error {
case "fifo": case "fifo":
case "socket": case "socket":
default: default:
err = fmt.Errorf("invalid node type %q", node.Type) err = errors.Errorf("invalid node type %q", node.Type)
} }
return err return err

View File

@ -5,22 +5,22 @@ import (
"syscall" "syscall"
"unsafe" "unsafe"
"restic/fs" "github.com/pkg/errors"
"github.com/juju/errors" "restic/fs"
) )
func (node Node) restoreSymlinkTimestamps(path string, utimes [2]syscall.Timespec) error { func (node Node) restoreSymlinkTimestamps(path string, utimes [2]syscall.Timespec) error {
dir, err := fs.Open(filepath.Dir(path)) dir, err := fs.Open(filepath.Dir(path))
defer dir.Close() defer dir.Close()
if err != nil { if err != nil {
return err return errors.Wrap(err, "Open")
} }
err = utimesNanoAt(int(dir.Fd()), filepath.Base(path), utimes, AT_SYMLINK_NOFOLLOW) err = utimesNanoAt(int(dir.Fd()), filepath.Base(path), utimes, AT_SYMLINK_NOFOLLOW)
if err != nil { if err != nil {
return errors.Annotate(err, "UtimesNanoAt") return errors.Wrap(err, "UtimesNanoAt")
} }
return nil return nil

View File

@ -1,8 +1,9 @@
package restic package restic
import ( import (
"errors"
"syscall" "syscall"
"github.com/pkg/errors"
) )
// mknod() creates a filesystem node (file, device // mknod() creates a filesystem node (file, device

View File

@ -3,11 +3,12 @@ package pack
import ( import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"errors"
"fmt" "fmt"
"io" "io"
"sync" "sync"
"github.com/pkg/errors"
"restic/backend" "restic/backend"
"restic/crypto" "restic/crypto"
) )
@ -106,7 +107,7 @@ func (p *Packer) Add(t BlobType, id backend.ID, data []byte) (int, error) {
p.bytes += uint(n) p.bytes += uint(n)
p.blobs = append(p.blobs, c) p.blobs = append(p.blobs, c)
return n, err return n, errors.Wrap(err, "Write")
} }
var entrySize = uint(binary.Size(BlobType(0)) + binary.Size(uint32(0)) + backend.IDSize) var entrySize = uint(binary.Size(BlobType(0)) + binary.Size(uint32(0)) + backend.IDSize)
@ -141,7 +142,7 @@ func (p *Packer) Finalize() (uint, error) {
// append the header // append the header
n, err := p.wr.Write(encryptedHeader) n, err := p.wr.Write(encryptedHeader)
if err != nil { if err != nil {
return 0, err return 0, errors.Wrap(err, "Write")
} }
hdrBytes := bytesHeader + crypto.Extension hdrBytes := bytesHeader + crypto.Extension
@ -154,7 +155,7 @@ func (p *Packer) Finalize() (uint, error) {
// write length // write length
err = binary.Write(p.wr, binary.LittleEndian, uint32(uint(len(p.blobs))*entrySize+crypto.Extension)) err = binary.Write(p.wr, binary.LittleEndian, uint32(uint(len(p.blobs))*entrySize+crypto.Extension))
if err != nil { if err != nil {
return 0, err return 0, errors.Wrap(err, "binary.Write")
} }
bytesWritten += uint(binary.Size(uint32(0))) bytesWritten += uint(binary.Size(uint32(0)))
@ -181,12 +182,12 @@ func (p *Packer) writeHeader(wr io.Writer) (bytesWritten uint, err error) {
case Tree: case Tree:
entry.Type = 1 entry.Type = 1
default: default:
return 0, fmt.Errorf("invalid blob type %v", b.Type) return 0, errors.Errorf("invalid blob type %v", b.Type)
} }
err := binary.Write(wr, binary.LittleEndian, entry) err := binary.Write(wr, binary.LittleEndian, entry)
if err != nil { if err != nil {
return bytesWritten, err return bytesWritten, errors.Wrap(err, "binary.Write")
} }
bytesWritten += entrySize bytesWritten += entrySize
@ -236,7 +237,7 @@ func readHeaderLength(rd io.ReaderAt, size int64) (uint32, error) {
buf := make([]byte, binary.Size(uint32(0))) buf := make([]byte, binary.Size(uint32(0)))
n, err := rd.ReadAt(buf, off) n, err := rd.ReadAt(buf, off)
if err != nil { if err != nil {
return 0, err return 0, errors.Wrap(err, "ReadAt")
} }
if n != len(buf) { if n != len(buf) {
@ -267,7 +268,7 @@ func readHeader(rd io.ReaderAt, size int64) ([]byte, error) {
buf := make([]byte, int(hl)) buf := make([]byte, int(hl))
n, err := rd.ReadAt(buf, size-int64(hl)-int64(binary.Size(hl))) n, err := rd.ReadAt(buf, size-int64(hl)-int64(binary.Size(hl)))
if err != nil { if err != nil {
return nil, err return nil, errors.Wrap(err, "ReadAt")
} }
if n != len(buf) { if n != len(buf) {
@ -295,12 +296,12 @@ func List(k *crypto.Key, rd io.ReaderAt, size int64) (entries []Blob, err error)
for { for {
e := headerEntry{} e := headerEntry{}
err = binary.Read(hdrRd, binary.LittleEndian, &e) err = binary.Read(hdrRd, binary.LittleEndian, &e)
if err == io.EOF { if errors.Cause(err) == io.EOF {
break break
} }
if err != nil { if err != nil {
return nil, err return nil, errors.Wrap(err, "binary.Read")
} }
entry := Blob{ entry := Blob{
@ -315,7 +316,7 @@ func List(k *crypto.Key, rd io.ReaderAt, size int64) (entries []Blob, err error)
case 1: case 1:
entry.Type = Tree entry.Type = Tree
default: default:
return nil, fmt.Errorf("invalid type %d", e.Type) return nil, errors.Errorf("invalid type %d", e.Type)
} }
entries = append(entries, entry) entries = append(entries, entry)

View File

@ -1,12 +1,13 @@
package pipe package pipe
import ( import (
"errors"
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"sort" "sort"
"github.com/pkg/errors"
"restic/debug" "restic/debug"
"restic/fs" "restic/fs"
) )
@ -62,12 +63,12 @@ func (e Dir) Result() chan<- Result { return e.result }
func readDirNames(dirname string) ([]string, error) { func readDirNames(dirname string) ([]string, error) {
f, err := fs.Open(dirname) f, err := fs.Open(dirname)
if err != nil { if err != nil {
return nil, err return nil, errors.Wrap(err, "Open")
} }
names, err := f.Readdirnames(-1) names, err := f.Readdirnames(-1)
f.Close() f.Close()
if err != nil { if err != nil {
return nil, err return nil, errors.Wrap(err, "Readdirnames")
} }
sort.Strings(names) sort.Strings(names)
return names, nil return names, nil
@ -93,6 +94,7 @@ func walk(basedir, dir string, selectFunc SelectFunc, done <-chan struct{}, jobs
info, err := fs.Lstat(dir) info, err := fs.Lstat(dir)
if err != nil { if err != nil {
err = errors.Wrap(err, "Lstat")
debug.Log("pipe.walk", "error for %v: %v, res %p", dir, err, res) debug.Log("pipe.walk", "error for %v: %v, res %p", dir, err, res)
select { select {
case jobs <- Dir{basedir: basedir, path: relpath, info: info, error: err, result: res}: case jobs <- Dir{basedir: basedir, path: relpath, info: info, error: err, result: res}:
@ -146,6 +148,7 @@ func walk(basedir, dir string, selectFunc SelectFunc, done <-chan struct{}, jobs
entries = append(entries, ch) entries = append(entries, ch)
if statErr != nil { if statErr != nil {
statErr = errors.Wrap(statErr, "Lstat")
debug.Log("pipe.walk", "sending file job for %v, err %v, res %p", subpath, err, res) debug.Log("pipe.walk", "sending file job for %v, err %v, res %p", subpath, err, res)
select { select {
case jobs <- Entry{info: fi, error: statErr, basedir: basedir, path: filepath.Join(relpath, name), result: ch}: case jobs <- Entry{info: fi, error: statErr, basedir: basedir, path: filepath.Join(relpath, name), result: ch}:

View File

@ -2,10 +2,11 @@ package restic
import ( import (
"fmt" "fmt"
"golang.org/x/crypto/ssh/terminal"
"os" "os"
"sync" "sync"
"time" "time"
"golang.org/x/crypto/ssh/terminal"
) )
const minTickerTime = time.Second / 60 const minTickerTime = time.Second / 60

View File

@ -4,10 +4,11 @@ import (
"crypto/rand" "crypto/rand"
"crypto/sha256" "crypto/sha256"
"encoding/hex" "encoding/hex"
"errors"
"io" "io"
"testing" "testing"
"github.com/pkg/errors"
"restic/backend" "restic/backend"
"restic/debug" "restic/debug"
@ -48,13 +49,13 @@ func CreateConfig() (Config, error) {
cfg.ChunkerPolynomial, err = chunker.RandomPolynomial() cfg.ChunkerPolynomial, err = chunker.RandomPolynomial()
if err != nil { if err != nil {
return Config{}, err return Config{}, errors.Wrap(err, "chunker.RandomPolynomial")
} }
newID := make([]byte, repositoryIDSize) newID := make([]byte, repositoryIDSize)
_, err = io.ReadFull(rand.Reader, newID) _, err = io.ReadFull(rand.Reader, newID)
if err != nil { if err != nil {
return Config{}, err return Config{}, errors.Wrap(err, "io.ReadFull")
} }
cfg.ID = hex.EncodeToString(newID) cfg.ID = hex.EncodeToString(newID)

View File

@ -3,12 +3,13 @@ package repository
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io" "io"
"sync" "sync"
"time" "time"
"github.com/pkg/errors"
"restic/backend" "restic/backend"
"restic/crypto" "restic/crypto"
"restic/debug" "restic/debug"
@ -139,7 +140,7 @@ func (idx *Index) Lookup(id backend.ID, tpe pack.BlobType) (blobs []PackedBlob,
} }
debug.Log("Index.Lookup", "id %v not found", id.Str()) debug.Log("Index.Lookup", "id %v not found", id.Str())
return nil, fmt.Errorf("id %v not found in index", id) return nil, errors.Errorf("id %v not found in index", id)
} }
// ListPack returns a list of blobs contained in a pack. // ListPack returns a list of blobs contained in a pack.
@ -326,7 +327,7 @@ func (idx *Index) generatePackList() ([]*packJSON, error) {
if blob.packID.IsNull() { if blob.packID.IsNull() {
debug.Log("Index.generatePackList", "blob %v has no packID! (offset %v, length %v)", debug.Log("Index.generatePackList", "blob %v has no packID! (offset %v, length %v)",
h, blob.offset, blob.length) h, blob.offset, blob.length)
return nil, fmt.Errorf("unable to serialize index: pack for blob %v hasn't been written yet", h) return nil, errors.Errorf("unable to serialize index: pack for blob %v hasn't been written yet", h)
} }
// see if pack is already in map // see if pack is already in map
@ -455,7 +456,7 @@ func (idx *Index) Dump(w io.Writer) error {
_, err = w.Write(append(buf, '\n')) _, err = w.Write(append(buf, '\n'))
if err != nil { if err != nil {
return err return errors.Wrap(err, "Write")
} }
debug.Log("Index.Dump", "done") debug.Log("Index.Dump", "done")
@ -491,7 +492,7 @@ func DecodeIndex(rd io.Reader) (idx *Index, err error) {
err = ErrOldIndexFormat err = ErrOldIndexFormat
} }
return nil, err return nil, errors.Wrap(err, "Decode")
} }
idx = NewIndex() idx = NewIndex()
@ -510,7 +511,7 @@ func DecodeIndex(rd io.Reader) (idx *Index, err error) {
idx.final = true idx.final = true
debug.Log("Index.DecodeIndex", "done") debug.Log("Index.DecodeIndex", "done")
return idx, err return idx, nil
} }
// DecodeOldIndex loads and unserializes an index in the old format from rd. // DecodeOldIndex loads and unserializes an index in the old format from rd.
@ -522,7 +523,7 @@ func DecodeOldIndex(rd io.Reader) (idx *Index, err error) {
err = dec.Decode(&list) err = dec.Decode(&list)
if err != nil { if err != nil {
debug.Log("Index.DecodeOldIndex", "Error %#v", err) debug.Log("Index.DecodeOldIndex", "Error %#v", err)
return nil, err return nil, errors.Wrap(err, "Decode")
} }
idx = NewIndex() idx = NewIndex()
@ -540,7 +541,7 @@ func DecodeOldIndex(rd io.Reader) (idx *Index, err error) {
idx.final = true idx.final = true
debug.Log("Index.DecodeOldIndex", "done") debug.Log("Index.DecodeOldIndex", "done")
return idx, err return idx, nil
} }
// LoadIndexWithDecoder loads the index and decodes it with fn. // LoadIndexWithDecoder loads the index and decodes it with fn.

View File

@ -2,12 +2,13 @@ package repository
import ( import (
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"os" "os"
"os/user" "os/user"
"time" "time"
"github.com/pkg/errors"
"restic/backend" "restic/backend"
"restic/crypto" "restic/crypto"
"restic/debug" "restic/debug"
@ -79,7 +80,7 @@ func OpenKey(s *Repository, name string, password string) (*Key, error) {
} }
k.user, err = crypto.KDF(params, k.Salt, password) k.user, err = crypto.KDF(params, k.Salt, password)
if err != nil { if err != nil {
return nil, err return nil, errors.Wrap(err, "crypto.KDF")
} }
// decrypt master keys // decrypt master keys
@ -93,7 +94,7 @@ func OpenKey(s *Repository, name string, password string) (*Key, error) {
err = json.Unmarshal(buf, k.master) err = json.Unmarshal(buf, k.master)
if err != nil { if err != nil {
debug.Log("OpenKey", "Unmarshal() returned error %v", err) debug.Log("OpenKey", "Unmarshal() returned error %v", err)
return nil, err return nil, errors.Wrap(err, "Unmarshal")
} }
k.name = name k.name = name
@ -125,7 +126,7 @@ func SearchKey(s *Repository, password string, maxKeys int) (*Key, error) {
debug.Log("SearchKey", "key %v returned error %v", name[:12], err) debug.Log("SearchKey", "key %v returned error %v", name[:12], err)
// ErrUnauthenticated means the password is wrong, try the next key // ErrUnauthenticated means the password is wrong, try the next key
if err == crypto.ErrUnauthenticated { if errors.Cause(err) == crypto.ErrUnauthenticated {
continue continue
} }
@ -150,7 +151,7 @@ func LoadKey(s *Repository, name string) (k *Key, err error) {
k = &Key{} k = &Key{}
err = json.Unmarshal(data, k) err = json.Unmarshal(data, k)
if err != nil { if err != nil {
return nil, err return nil, errors.Wrap(err, "Unmarshal")
} }
return k, nil return k, nil
@ -162,7 +163,7 @@ func AddKey(s *Repository, password string, template *crypto.Key) (*Key, error)
if KDFParams == nil { if KDFParams == nil {
p, err := crypto.Calibrate(KDFTimeout, KDFMemory) p, err := crypto.Calibrate(KDFTimeout, KDFMemory)
if err != nil { if err != nil {
return nil, err return nil, errors.Wrap(err, "Calibrate")
} }
KDFParams = &p KDFParams = &p
@ -211,7 +212,7 @@ func AddKey(s *Repository, password string, template *crypto.Key) (*Key, error)
// encrypt master keys (as json) with user key // encrypt master keys (as json) with user key
buf, err := json.Marshal(newkey.master) buf, err := json.Marshal(newkey.master)
if err != nil { if err != nil {
return nil, err return nil, errors.Wrap(err, "Marshal")
} }
newkey.Data, err = crypto.Encrypt(newkey.user, nil, buf) newkey.Data, err = crypto.Encrypt(newkey.user, nil, buf)
@ -219,7 +220,7 @@ func AddKey(s *Repository, password string, template *crypto.Key) (*Key, error)
// dump as json // dump as json
buf, err = json.Marshal(newkey) buf, err = json.Marshal(newkey)
if err != nil { if err != nil {
return nil, err return nil, errors.Wrap(err, "Marshal")
} }
// store in repository and return // store in repository and return

View File

@ -1,9 +1,10 @@
package repository package repository
import ( import (
"fmt"
"sync" "sync"
"github.com/pkg/errors"
"restic/backend" "restic/backend"
"restic/debug" "restic/debug"
"restic/pack" "restic/pack"
@ -37,7 +38,7 @@ func (mi *MasterIndex) Lookup(id backend.ID, tpe pack.BlobType) (blobs []PackedB
} }
debug.Log("MasterIndex.Lookup", "id %v not found in any index", id.Str()) debug.Log("MasterIndex.Lookup", "id %v not found in any index", id.Str())
return nil, fmt.Errorf("id %v not found in any index", id) return nil, errors.Errorf("id %v not found in any index", id)
} }
// LookupSize queries all known Indexes for the ID and returns the first match. // LookupSize queries all known Indexes for the ID and returns the first match.
@ -52,7 +53,7 @@ func (mi *MasterIndex) LookupSize(id backend.ID, tpe pack.BlobType) (uint, error
} }
} }
return 0, fmt.Errorf("id %v not found in any index", id) return 0, errors.Errorf("id %v not found in any index", id)
} }
// ListPack returns the list of blobs in a pack. The first matching index is // ListPack returns the list of blobs in a pack. The first matching index is

View File

@ -1,12 +1,13 @@
package repository package repository
import ( import (
"fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"os" "os"
"sync" "sync"
"github.com/pkg/errors"
"restic/backend" "restic/backend"
"restic/crypto" "restic/crypto"
"restic/debug" "restic/debug"
@ -70,7 +71,7 @@ func (r *packerManager) findPacker(size uint) (packer *pack.Packer, err error) {
debug.Log("Repo.findPacker", "create new pack for %d bytes", size) debug.Log("Repo.findPacker", "create new pack for %d bytes", size)
tmpfile, err := ioutil.TempFile("", "restic-temp-pack-") tmpfile, err := ioutil.TempFile("", "restic-temp-pack-")
if err != nil { if err != nil {
return nil, err return nil, errors.Wrap(err, "ioutil.TempFile")
} }
return pack.NewPacker(r.key, tmpfile), nil return pack.NewPacker(r.key, tmpfile), nil
@ -96,18 +97,21 @@ func (r *Repository) savePacker(p *pack.Packer) error {
tmpfile := p.Writer().(*os.File) tmpfile := p.Writer().(*os.File)
f, err := fs.Open(tmpfile.Name()) f, err := fs.Open(tmpfile.Name())
if err != nil { if err != nil {
return err return errors.Wrap(err, "Open")
} }
data := make([]byte, n) data := make([]byte, n)
m, err := io.ReadFull(f, data) m, err := io.ReadFull(f, data)
if err != nil {
return errors.Wrap(err, "ReadFul")
}
if uint(m) != n { if uint(m) != n {
return fmt.Errorf("read wrong number of bytes from %v: want %v, got %v", tmpfile.Name(), n, m) return errors.Errorf("read wrong number of bytes from %v: want %v, got %v", tmpfile.Name(), n, m)
} }
if err = f.Close(); err != nil { if err = f.Close(); err != nil {
return err return errors.Wrap(err, "Close")
} }
id := backend.Hash(data) id := backend.Hash(data)
@ -123,7 +127,7 @@ func (r *Repository) savePacker(p *pack.Packer) error {
err = fs.Remove(tmpfile.Name()) err = fs.Remove(tmpfile.Name())
if err != nil { if err != nil {
return err return errors.Wrap(err, "Remove")
} }
// update blobs in the index // update blobs in the index

View File

@ -1,11 +1,12 @@
package repository_test package repository_test
import ( import (
"errors"
"math/rand" "math/rand"
"testing" "testing"
"time" "time"
"github.com/pkg/errors"
"restic/backend" "restic/backend"
"restic/repository" "restic/repository"
. "restic/test" . "restic/test"

View File

@ -3,6 +3,8 @@ package repository
import ( import (
"io" "io"
"math/rand" "math/rand"
"github.com/pkg/errors"
) )
// RandReader allows reading from a rand.Rand. // RandReader allows reading from a rand.Rand.
@ -56,7 +58,7 @@ func (rd *RandReader) Read(p []byte) (int, error) {
n, err := rd.read(p[:l]) n, err := rd.read(p[:l])
pos += n pos += n
if err != nil { if err != nil {
return pos, err return pos, errors.Wrap(err, "Read")
} }
p = p[n:] p = p[n:]
@ -64,7 +66,7 @@ func (rd *RandReader) Read(p []byte) (int, error) {
rd.buf = rd.buf[:7] rd.buf = rd.buf[:7]
n, err = rd.read(rd.buf) n, err = rd.read(rd.buf)
if err != nil { if err != nil {
return pos, err return pos, errors.Wrap(err, "Read")
} }
// copy the remaining bytes from the buffer to p // copy the remaining bytes from the buffer to p

View File

@ -7,6 +7,8 @@ import (
"restic/crypto" "restic/crypto"
"restic/debug" "restic/debug"
"restic/pack" "restic/pack"
"github.com/pkg/errors"
) )
// Repack takes a list of packs together with a list of blobs contained in // Repack takes a list of packs together with a list of blobs contained in
@ -22,7 +24,7 @@ func Repack(repo *Repository, packs backend.IDSet, keepBlobs pack.BlobSet) (err
h := backend.Handle{Type: backend.Data, Name: packID.String()} h := backend.Handle{Type: backend.Data, Name: packID.String()}
l, err := repo.Backend().Load(h, buf[:cap(buf)], 0) l, err := repo.Backend().Load(h, buf[:cap(buf)], 0)
if err == io.ErrUnexpectedEOF { if errors.Cause(err) == io.ErrUnexpectedEOF {
err = nil err = nil
buf = buf[:l] buf = buf[:l]
} }

View File

@ -3,11 +3,12 @@ package repository
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io" "io"
"os" "os"
"github.com/pkg/errors"
"restic/backend" "restic/backend"
"restic/crypto" "restic/crypto"
"restic/debug" "restic/debug"
@ -101,6 +102,7 @@ func (r *Repository) LoadBlob(id backend.ID, t pack.BlobType, plaintextBuf []byt
return nil, err return nil, err
} }
var lastError error
for _, blob := range blobs { for _, blob := range blobs {
debug.Log("Repo.LoadBlob", "id %v found: %v", id.Str(), blob) debug.Log("Repo.LoadBlob", "id %v found: %v", id.Str(), blob)
@ -114,33 +116,38 @@ func (r *Repository) LoadBlob(id backend.ID, t pack.BlobType, plaintextBuf []byt
n, err := r.be.Load(h, ciphertextBuf, int64(blob.Offset)) n, err := r.be.Load(h, ciphertextBuf, int64(blob.Offset))
if err != nil { if err != nil {
debug.Log("Repo.LoadBlob", "error loading blob %v: %v", blob, err) debug.Log("Repo.LoadBlob", "error loading blob %v: %v", blob, err)
fmt.Fprintf(os.Stderr, "error loading blob %v: %v", id, err) lastError = err
continue continue
} }
if uint(n) != blob.Length { if uint(n) != blob.Length {
debug.Log("Repo.LoadBlob", "error loading blob %v: wrong length returned, want %d, got %d", lastError = errors.Errorf("error loading blob %v: wrong length returned, want %d, got %d",
blob.Length, uint(n)) id.Str(), blob.Length, uint(n))
debug.Log("Repo.LoadBlob", "lastError: %v", lastError)
continue continue
} }
// decrypt // decrypt
plaintextBuf, err = r.decryptTo(plaintextBuf, ciphertextBuf) plaintextBuf, err = r.decryptTo(plaintextBuf, ciphertextBuf)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "decrypting blob %v failed: %v", id, err) lastError = errors.Errorf("decrypting blob %v failed: %v", id, err)
continue continue
} }
// check hash // check hash
if !backend.Hash(plaintextBuf).Equal(id) { if !backend.Hash(plaintextBuf).Equal(id) {
fmt.Fprintf(os.Stderr, "blob %v returned invalid hash", id) lastError = errors.Errorf("blob %v returned invalid hash", id)
continue continue
} }
return plaintextBuf, nil return plaintextBuf, nil
} }
return nil, fmt.Errorf("loading blob %v from %v packs failed", id.Str(), len(blobs)) if lastError != nil {
return nil, lastError
}
return nil, errors.Errorf("loading blob %v from %v packs failed", id.Str(), len(blobs))
} }
// closeOrErr calls cl.Close() and sets err to the returned error value if // closeOrErr calls cl.Close() and sets err to the returned error value if
@ -237,7 +244,7 @@ func (r *Repository) SaveJSON(t pack.BlobType, item interface{}) (backend.ID, er
enc := json.NewEncoder(wr) enc := json.NewEncoder(wr)
err := enc.Encode(item) err := enc.Encode(item)
if err != nil { if err != nil {
return backend.ID{}, fmt.Errorf("json.Encode: %v", err) return backend.ID{}, errors.Errorf("json.Encode: %v", err)
} }
buf = wr.Bytes() buf = wr.Bytes()
@ -250,7 +257,7 @@ func (r *Repository) SaveJSONUnpacked(t backend.Type, item interface{}) (backend
debug.Log("Repo.SaveJSONUnpacked", "save new blob %v", t) debug.Log("Repo.SaveJSONUnpacked", "save new blob %v", t)
plaintext, err := json.Marshal(item) plaintext, err := json.Marshal(item)
if err != nil { if err != nil {
return backend.ID{}, fmt.Errorf("json.Encode: %v", err) return backend.ID{}, errors.Wrap(err, "json.Marshal")
} }
return r.SaveUnpacked(t, plaintext) return r.SaveUnpacked(t, plaintext)
@ -396,7 +403,7 @@ func LoadIndex(repo *Repository, id backend.ID) (*Index, error) {
return idx, nil return idx, nil
} }
if err == ErrOldIndexFormat { if errors.Cause(err) == ErrOldIndexFormat {
fmt.Fprintf(os.Stderr, "index %v has old format\n", id.Str()) fmt.Fprintf(os.Stderr, "index %v has old format\n", id.Str())
return LoadIndexWithDecoder(repo, id, DecodeOldIndex) return LoadIndexWithDecoder(repo, id, DecodeOldIndex)
} }

View File

@ -1,16 +1,15 @@
package restic package restic
import ( import (
"fmt"
"os" "os"
"path/filepath" "path/filepath"
"github.com/pkg/errors"
"restic/backend" "restic/backend"
"restic/debug" "restic/debug"
"restic/fs" "restic/fs"
"restic/repository" "restic/repository"
"github.com/juju/errors"
) )
// Restorer is used to restore a snapshot to a directory. // Restorer is used to restore a snapshot to a directory.
@ -35,7 +34,7 @@ func NewRestorer(repo *repository.Repository, id backend.ID) (*Restorer, error)
r.sn, err = LoadSnapshot(repo, id) r.sn, err = LoadSnapshot(repo, id)
if err != nil { if err != nil {
return nil, errors.Annotate(err, "load snapshot for restorer") return nil, err
} }
return r, nil return r, nil
@ -44,7 +43,7 @@ func NewRestorer(repo *repository.Repository, id backend.ID) (*Restorer, error)
func (res *Restorer) restoreTo(dst string, dir string, treeID backend.ID) error { func (res *Restorer) restoreTo(dst string, dir string, treeID backend.ID) error {
tree, err := LoadTree(res.repo, treeID) tree, err := LoadTree(res.repo, treeID)
if err != nil { if err != nil {
return res.Error(dir, nil, errors.Annotate(err, "LoadTree")) return res.Error(dir, nil, err)
} }
for _, node := range tree.Nodes { for _, node := range tree.Nodes {
@ -61,13 +60,13 @@ func (res *Restorer) restoreTo(dst string, dir string, treeID backend.ID) error
if node.Type == "dir" { if node.Type == "dir" {
if node.Subtree == nil { if node.Subtree == nil {
return fmt.Errorf("Dir without subtree in tree %v", treeID.Str()) return errors.Errorf("Dir without subtree in tree %v", treeID.Str())
} }
subp := filepath.Join(dir, node.Name) subp := filepath.Join(dir, node.Name)
err = res.restoreTo(dst, subp, *node.Subtree) err = res.restoreTo(dst, subp, *node.Subtree)
if err != nil { if err != nil {
err = res.Error(subp, node, errors.Annotate(err, "restore subtree")) err = res.Error(subp, node, err)
if err != nil { if err != nil {
return err return err
} }
@ -101,14 +100,14 @@ func (res *Restorer) restoreNodeTo(node *Node, dir string, dst string) error {
// Create parent directories and retry // Create parent directories and retry
err = fs.MkdirAll(filepath.Dir(dstPath), 0700) err = fs.MkdirAll(filepath.Dir(dstPath), 0700)
if err == nil || err == os.ErrExist { if err == nil || os.IsExist(errors.Cause(err)) {
err = node.CreateAt(dstPath, res.repo) err = node.CreateAt(dstPath, res.repo)
} }
} }
if err != nil { if err != nil {
debug.Log("Restorer.restoreNodeTo", "error %v", err) debug.Log("Restorer.restoreNodeTo", "error %v", err)
err = res.Error(dstPath, node, errors.Annotate(err, "create node")) err = res.Error(dstPath, node, err)
if err != nil { if err != nil {
return err return err
} }

View File

@ -1,13 +1,14 @@
package restic package restic
import ( import (
"errors"
"fmt" "fmt"
"os" "os"
"os/user" "os/user"
"path/filepath" "path/filepath"
"time" "time"
"github.com/pkg/errors"
"restic/backend" "restic/backend"
"restic/repository" "restic/repository"
) )
@ -140,7 +141,7 @@ func FindLatestSnapshot(repo *repository.Repository, targets []string, source st
for snapshotID := range repo.List(backend.Snapshot, make(chan struct{})) { for snapshotID := range repo.List(backend.Snapshot, make(chan struct{})) {
snapshot, err := LoadSnapshot(repo, snapshotID) snapshot, err := LoadSnapshot(repo, snapshotID)
if err != nil { if err != nil {
return backend.ID{}, fmt.Errorf("Error listing snapshot: %v", err) return backend.ID{}, errors.Errorf("Error listing snapshot: %v", err)
} }
if snapshot.Time.After(latest) && SamePaths(snapshot.Paths, targets) && (source == "" || source == snapshot.Hostname) { if snapshot.Time.After(latest) && SamePaths(snapshot.Paths, targets) && (source == "" || source == snapshot.Hostname) {
latest = snapshot.Time latest = snapshot.Time

View File

@ -11,6 +11,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/pkg/errors"
"github.com/restic/chunker" "github.com/restic/chunker"
) )
@ -34,7 +35,7 @@ func (fs fakeFileSystem) saveFile(rd io.Reader) (blobs backend.IDs) {
for { for {
chunk, err := ch.Next(getBuf()) chunk, err := ch.Next(getBuf())
if err == io.EOF { if errors.Cause(err) == io.EOF {
break break
} }

View File

@ -1,10 +1,11 @@
package restic package restic
import ( import (
"errors"
"fmt" "fmt"
"sort" "sort"
"github.com/pkg/errors"
"restic/backend" "restic/backend"
"restic/debug" "restic/debug"
"restic/pack" "restic/pack"

View File

@ -1,9 +1,10 @@
package worker_test package worker_test
import ( import (
"errors"
"testing" "testing"
"github.com/pkg/errors"
"restic/worker" "restic/worker"
) )

6
vendor/manifest vendored
View File

@ -19,12 +19,6 @@
"revision": "1b89bf73cd2c3a911d7b2a279ab085c4a18cf539", "revision": "1b89bf73cd2c3a911d7b2a279ab085c4a18cf539",
"branch": "HEAD" "branch": "HEAD"
}, },
{
"importpath": "github.com/juju/errors",
"repository": "https://github.com/juju/errors",
"revision": "4567a5e69fd3130ca0d89f69478e7ac025b67452",
"branch": "HEAD"
},
{ {
"importpath": "github.com/kr/fs", "importpath": "github.com/kr/fs",
"repository": "https://github.com/kr/fs", "repository": "https://github.com/kr/fs",

View File

@ -1,191 +0,0 @@
All files in this repository are licensed as follows. If you contribute
to this repository, it is assumed that you license your contribution
under the same license unless you state otherwise.
All files Copyright (C) 2015 Canonical Ltd. unless otherwise specified in the file.
This software is licensed under the LGPLv3, included below.
As a special exception to the GNU Lesser General Public License version 3
("LGPL3"), the copyright holders of this Library give you permission to
convey to a third party a Combined Work that links statically or dynamically
to this Library without providing any Minimal Corresponding Source or
Minimal Application Code as set out in 4d or providing the installation
information set out in section 4e, provided that you comply with the other
provisions of LGPL3 and provided that you meet, for the Application the
terms and conditions of the license(s) which apply to the Application.
Except as stated in this special exception, the provisions of LGPL3 will
continue to comply in full to this Library. If you modify this Library, you
may apply this exception to your version of this Library, but you are not
obliged to do so. If you do not wish to do so, delete this exception
statement from your version. This exception does not (and cannot) modify any
license terms which apply to the Application, with which you must still
comply.
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.

View File

@ -1,11 +0,0 @@
default: check
check:
go test && go test -compiler gccgo
docs:
godoc2md github.com/juju/errors > README.md
sed -i 's|\[godoc-link-here\]|[![GoDoc](https://godoc.org/github.com/juju/errors?status.svg)](https://godoc.org/github.com/juju/errors)|' README.md
.PHONY: default check docs

View File

@ -1,536 +0,0 @@
# errors
import "github.com/juju/errors"
[![GoDoc](https://godoc.org/github.com/juju/errors?status.svg)](https://godoc.org/github.com/juju/errors)
The juju/errors provides an easy way to annotate errors without losing the
orginal error context.
The exported `New` and `Errorf` functions are designed to replace the
`errors.New` and `fmt.Errorf` functions respectively. The same underlying
error is there, but the package also records the location at which the error
was created.
A primary use case for this library is to add extra context any time an
error is returned from a function.
if err := SomeFunc(); err != nil {
return err
}
This instead becomes:
if err := SomeFunc(); err != nil {
return errors.Trace(err)
}
which just records the file and line number of the Trace call, or
if err := SomeFunc(); err != nil {
return errors.Annotate(err, "more context")
}
which also adds an annotation to the error.
When you want to check to see if an error is of a particular type, a helper
function is normally exported by the package that returned the error, like the
`os` package does. The underlying cause of the error is available using the
`Cause` function.
os.IsNotExist(errors.Cause(err))
The result of the `Error()` call on an annotated error is the annotations joined
with colons, then the result of the `Error()` method for the underlying error
that was the cause.
err := errors.Errorf("original")
err = errors.Annotatef(err, "context")
err = errors.Annotatef(err, "more context")
err.Error() -> "more context: context: original"
Obviously recording the file, line and functions is not very useful if you
cannot get them back out again.
errors.ErrorStack(err)
will return something like:
first error
github.com/juju/errors/annotation_test.go:193:
github.com/juju/errors/annotation_test.go:194: annotation
github.com/juju/errors/annotation_test.go:195:
github.com/juju/errors/annotation_test.go:196: more context
github.com/juju/errors/annotation_test.go:197:
The first error was generated by an external system, so there was no location
associated. The second, fourth, and last lines were generated with Trace calls,
and the other two through Annotate.
Sometimes when responding to an error you want to return a more specific error
for the situation.
if err := FindField(field); err != nil {
return errors.Wrap(err, errors.NotFoundf(field))
}
This returns an error where the complete error stack is still available, and
`errors.Cause()` will return the `NotFound` error.
## func AlreadyExistsf
``` go
func AlreadyExistsf(format string, args ...interface{}) error
```
AlreadyExistsf returns an error which satisfies IsAlreadyExists().
## func Annotate
``` go
func Annotate(other error, message string) error
```
Annotate is used to add extra context to an existing error. The location of
the Annotate call is recorded with the annotations. The file, line and
function are also recorded.
For example:
if err := SomeFunc(); err != nil {
return errors.Annotate(err, "failed to frombulate")
}
## func Annotatef
``` go
func Annotatef(other error, format string, args ...interface{}) error
```
Annotatef is used to add extra context to an existing error. The location of
the Annotate call is recorded with the annotations. The file, line and
function are also recorded.
For example:
if err := SomeFunc(); err != nil {
return errors.Annotatef(err, "failed to frombulate the %s", arg)
}
## func Cause
``` go
func Cause(err error) error
```
Cause returns the cause of the given error. This will be either the
original error, or the result of a Wrap or Mask call.
Cause is the usual way to diagnose errors that may have been wrapped by
the other errors functions.
## func DeferredAnnotatef
``` go
func DeferredAnnotatef(err *error, format string, args ...interface{})
```
DeferredAnnotatef annotates the given error (when it is not nil) with the given
format string and arguments (like fmt.Sprintf). If *err is nil, DeferredAnnotatef
does nothing. This method is used in a defer statement in order to annotate any
resulting error with the same message.
For example:
defer DeferredAnnotatef(&err, "failed to frombulate the %s", arg)
## func Details
``` go
func Details(err error) string
```
Details returns information about the stack of errors wrapped by err, in
the format:
[{filename:99: error one} {otherfile:55: cause of error one}]
This is a terse alternative to ErrorStack as it returns a single line.
## func ErrorStack
``` go
func ErrorStack(err error) string
```
ErrorStack returns a string representation of the annotated error. If the
error passed as the parameter is not an annotated error, the result is
simply the result of the Error() method on that error.
If the error is an annotated error, a multi-line string is returned where
each line represents one entry in the annotation stack. The full filename
from the call stack is used in the output.
first error
github.com/juju/errors/annotation_test.go:193:
github.com/juju/errors/annotation_test.go:194: annotation
github.com/juju/errors/annotation_test.go:195:
github.com/juju/errors/annotation_test.go:196: more context
github.com/juju/errors/annotation_test.go:197:
## func Errorf
``` go
func Errorf(format string, args ...interface{}) error
```
Errorf creates a new annotated error and records the location that the
error is created. This should be a drop in replacement for fmt.Errorf.
For example:
return errors.Errorf("validation failed: %s", message)
## func IsAlreadyExists
``` go
func IsAlreadyExists(err error) bool
```
IsAlreadyExists reports whether the error was created with
AlreadyExistsf() or NewAlreadyExists().
## func IsNotFound
``` go
func IsNotFound(err error) bool
```
IsNotFound reports whether err was created with NotFoundf() or
NewNotFound().
## func IsNotImplemented
``` go
func IsNotImplemented(err error) bool
```
IsNotImplemented reports whether err was created with
NotImplementedf() or NewNotImplemented().
## func IsNotSupported
``` go
func IsNotSupported(err error) bool
```
IsNotSupported reports whether the error was created with
NotSupportedf() or NewNotSupported().
## func IsNotValid
``` go
func IsNotValid(err error) bool
```
IsNotValid reports whether the error was created with NotValidf() or
NewNotValid().
## func IsUnauthorized
``` go
func IsUnauthorized(err error) bool
```
IsUnauthorized reports whether err was created with Unauthorizedf() or
NewUnauthorized().
## func Mask
``` go
func Mask(other error) error
```
Mask hides the underlying error type, and records the location of the masking.
## func Maskf
``` go
func Maskf(other error, format string, args ...interface{}) error
```
Mask masks the given error with the given format string and arguments (like
fmt.Sprintf), returning a new error that maintains the error stack, but
hides the underlying error type. The error string still contains the full
annotations. If you want to hide the annotations, call Wrap.
## func New
``` go
func New(message string) error
```
New is a drop in replacement for the standard libary errors module that records
the location that the error is created.
For example:
return errors.New("validation failed")
## func NewAlreadyExists
``` go
func NewAlreadyExists(err error, msg string) error
```
NewAlreadyExists returns an error which wraps err and satisfies
IsAlreadyExists().
## func NewNotFound
``` go
func NewNotFound(err error, msg string) error
```
NewNotFound returns an error which wraps err that satisfies
IsNotFound().
## func NewNotImplemented
``` go
func NewNotImplemented(err error, msg string) error
```
NewNotImplemented returns an error which wraps err and satisfies
IsNotImplemented().
## func NewNotSupported
``` go
func NewNotSupported(err error, msg string) error
```
NewNotSupported returns an error which wraps err and satisfies
IsNotSupported().
## func NewNotValid
``` go
func NewNotValid(err error, msg string) error
```
NewNotValid returns an error which wraps err and satisfies IsNotValid().
## func NewUnauthorized
``` go
func NewUnauthorized(err error, msg string) error
```
NewUnauthorized returns an error which wraps err and satisfies
IsUnauthorized().
## func NotFoundf
``` go
func NotFoundf(format string, args ...interface{}) error
```
NotFoundf returns an error which satisfies IsNotFound().
## func NotImplementedf
``` go
func NotImplementedf(format string, args ...interface{}) error
```
NotImplementedf returns an error which satisfies IsNotImplemented().
## func NotSupportedf
``` go
func NotSupportedf(format string, args ...interface{}) error
```
NotSupportedf returns an error which satisfies IsNotSupported().
## func NotValidf
``` go
func NotValidf(format string, args ...interface{}) error
```
NotValidf returns an error which satisfies IsNotValid().
## func Trace
``` go
func Trace(other error) error
```
Trace adds the location of the Trace call to the stack. The Cause of the
resulting error is the same as the error parameter. If the other error is
nil, the result will be nil.
For example:
if err := SomeFunc(); err != nil {
return errors.Trace(err)
}
## func Unauthorizedf
``` go
func Unauthorizedf(format string, args ...interface{}) error
```
Unauthorizedf returns an error which satisfies IsUnauthorized().
## func Wrap
``` go
func Wrap(other, newDescriptive error) error
```
Wrap changes the Cause of the error. The location of the Wrap call is also
stored in the error stack.
For example:
if err := SomeFunc(); err != nil {
newErr := &packageError{"more context", private_value}
return errors.Wrap(err, newErr)
}
## func Wrapf
``` go
func Wrapf(other, newDescriptive error, format string, args ...interface{}) error
```
Wrapf changes the Cause of the error, and adds an annotation. The location
of the Wrap call is also stored in the error stack.
For example:
if err := SomeFunc(); err != nil {
return errors.Wrapf(err, simpleErrorType, "invalid value %q", value)
}
## type Err
``` go
type Err struct {
// contains filtered or unexported fields
}
```
Err holds a description of an error along with information about
where the error was created.
It may be embedded in custom error types to add extra information that
this errors package can understand.
### func NewErr
``` go
func NewErr(format string, args ...interface{}) Err
```
NewErr is used to return an Err for the purpose of embedding in other
structures. The location is not specified, and needs to be set with a call
to SetLocation.
For example:
type FooError struct {
errors.Err
code int
}
func NewFooError(code int) error {
err := &FooError{errors.NewErr("foo"), code}
err.SetLocation(1)
return err
}
### func (\*Err) Cause
``` go
func (e *Err) Cause() error
```
The Cause of an error is the most recent error in the error stack that
meets one of these criteria: the original error that was raised; the new
error that was passed into the Wrap function; the most recently masked
error; or nil if the error itself is considered the Cause. Normally this
method is not invoked directly, but instead through the Cause stand alone
function.
### func (\*Err) Error
``` go
func (e *Err) Error() string
```
Error implements error.Error.
### func (\*Err) Location
``` go
func (e *Err) Location() (filename string, line int)
```
Location is the file and line of where the error was most recently
created or annotated.
### func (\*Err) Message
``` go
func (e *Err) Message() string
```
Message returns the message stored with the most recent location. This is
the empty string if the most recent call was Trace, or the message stored
with Annotate or Mask.
### func (\*Err) SetLocation
``` go
func (e *Err) SetLocation(callDepth int)
```
SetLocation records the source location of the error at callDepth stack
frames above the call.
### func (\*Err) StackTrace
``` go
func (e *Err) StackTrace() []string
```
StackTrace returns one string for each location recorded in the stack of
errors. The first value is the originating error, with a line for each
other annotation or tracing of the error.
### func (\*Err) Underlying
``` go
func (e *Err) Underlying() error
```
Underlying returns the previous error in the error stack, if any. A client
should not ever really call this method. It is used to build the error
stack and should not be introspected by client calls. Or more
specifically, clients should not depend on anything but the `Cause` of an
error.
- - -
Generated by [godoc2md](http://godoc.org/github.com/davecheney/godoc2md)

View File

@ -1,81 +0,0 @@
// Copyright 2013, 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
/*
[godoc-link-here]
The juju/errors provides an easy way to annotate errors without losing the
orginal error context.
The exported `New` and `Errorf` functions are designed to replace the
`errors.New` and `fmt.Errorf` functions respectively. The same underlying
error is there, but the package also records the location at which the error
was created.
A primary use case for this library is to add extra context any time an
error is returned from a function.
if err := SomeFunc(); err != nil {
return err
}
This instead becomes:
if err := SomeFunc(); err != nil {
return errors.Trace(err)
}
which just records the file and line number of the Trace call, or
if err := SomeFunc(); err != nil {
return errors.Annotate(err, "more context")
}
which also adds an annotation to the error.
When you want to check to see if an error is of a particular type, a helper
function is normally exported by the package that returned the error, like the
`os` package does. The underlying cause of the error is available using the
`Cause` function.
os.IsNotExist(errors.Cause(err))
The result of the `Error()` call on an annotated error is the annotations joined
with colons, then the result of the `Error()` method for the underlying error
that was the cause.
err := errors.Errorf("original")
err = errors.Annotatef(err, "context")
err = errors.Annotatef(err, "more context")
err.Error() -> "more context: context: original"
Obviously recording the file, line and functions is not very useful if you
cannot get them back out again.
errors.ErrorStack(err)
will return something like:
first error
github.com/juju/errors/annotation_test.go:193:
github.com/juju/errors/annotation_test.go:194: annotation
github.com/juju/errors/annotation_test.go:195:
github.com/juju/errors/annotation_test.go:196: more context
github.com/juju/errors/annotation_test.go:197:
The first error was generated by an external system, so there was no location
associated. The second, fourth, and last lines were generated with Trace calls,
and the other two through Annotate.
Sometimes when responding to an error you want to return a more specific error
for the situation.
if err := FindField(field); err != nil {
return errors.Wrap(err, errors.NotFoundf(field))
}
This returns an error where the complete error stack is still available, and
`errors.Cause()` will return the `NotFound` error.
*/
package errors

View File

@ -1,122 +0,0 @@
// Copyright 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package errors
import (
"fmt"
"reflect"
"runtime"
)
// Err holds a description of an error along with information about
// where the error was created.
//
// It may be embedded in custom error types to add extra information that
// this errors package can understand.
type Err struct {
// message holds an annotation of the error.
message string
// cause holds the cause of the error as returned
// by the Cause method.
cause error
// previous holds the previous error in the error stack, if any.
previous error
// file and line hold the source code location where the error was
// created.
file string
line int
}
// NewErr is used to return an Err for the purpose of embedding in other
// structures. The location is not specified, and needs to be set with a call
// to SetLocation.
//
// For example:
// type FooError struct {
// errors.Err
// code int
// }
//
// func NewFooError(code int) error {
// err := &FooError{errors.NewErr("foo"), code}
// err.SetLocation(1)
// return err
// }
func NewErr(format string, args ...interface{}) Err {
return Err{
message: fmt.Sprintf(format, args...),
}
}
// Location is the file and line of where the error was most recently
// created or annotated.
func (e *Err) Location() (filename string, line int) {
return e.file, e.line
}
// Underlying returns the previous error in the error stack, if any. A client
// should not ever really call this method. It is used to build the error
// stack and should not be introspected by client calls. Or more
// specifically, clients should not depend on anything but the `Cause` of an
// error.
func (e *Err) Underlying() error {
return e.previous
}
// The Cause of an error is the most recent error in the error stack that
// meets one of these criteria: the original error that was raised; the new
// error that was passed into the Wrap function; the most recently masked
// error; or nil if the error itself is considered the Cause. Normally this
// method is not invoked directly, but instead through the Cause stand alone
// function.
func (e *Err) Cause() error {
return e.cause
}
// Message returns the message stored with the most recent location. This is
// the empty string if the most recent call was Trace, or the message stored
// with Annotate or Mask.
func (e *Err) Message() string {
return e.message
}
// Error implements error.Error.
func (e *Err) Error() string {
// We want to walk up the stack of errors showing the annotations
// as long as the cause is the same.
err := e.previous
if !sameError(Cause(err), e.cause) && e.cause != nil {
err = e.cause
}
switch {
case err == nil:
return e.message
case e.message == "":
return err.Error()
}
return fmt.Sprintf("%s: %v", e.message, err)
}
// SetLocation records the source location of the error at callDepth stack
// frames above the call.
func (e *Err) SetLocation(callDepth int) {
_, file, line, _ := runtime.Caller(callDepth + 1)
e.file = trimGoPath(file)
e.line = line
}
// StackTrace returns one string for each location recorded in the stack of
// errors. The first value is the originating error, with a line for each
// other annotation or tracing of the error.
func (e *Err) StackTrace() []string {
return errorStack(e)
}
// Ideally we'd have a way to check identity, but deep equals will do.
func sameError(e1, e2 error) bool {
return reflect.DeepEqual(e1, e2)
}

View File

@ -1,161 +0,0 @@
// Copyright 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package errors_test
import (
"fmt"
"runtime"
jc "github.com/juju/testing/checkers"
gc "gopkg.in/check.v1"
"github.com/juju/errors"
)
type errorsSuite struct{}
var _ = gc.Suite(&errorsSuite{})
var someErr = errors.New("some error") //err varSomeErr
func (*errorsSuite) TestErrorString(c *gc.C) {
for i, test := range []struct {
message string
generator func() error
expected string
}{
{
message: "uncomparable errors",
generator: func() error {
err := errors.Annotatef(newNonComparableError("uncomparable"), "annotation")
return errors.Annotatef(err, "another")
},
expected: "another: annotation: uncomparable",
}, {
message: "Errorf",
generator: func() error {
return errors.Errorf("first error")
},
expected: "first error",
}, {
message: "annotated error",
generator: func() error {
err := errors.Errorf("first error")
return errors.Annotatef(err, "annotation")
},
expected: "annotation: first error",
}, {
message: "test annotation format",
generator: func() error {
err := errors.Errorf("first %s", "error")
return errors.Annotatef(err, "%s", "annotation")
},
expected: "annotation: first error",
}, {
message: "wrapped error",
generator: func() error {
err := newError("first error")
return errors.Wrap(err, newError("detailed error"))
},
expected: "detailed error",
}, {
message: "wrapped annotated error",
generator: func() error {
err := errors.Errorf("first error")
err = errors.Annotatef(err, "annotated")
return errors.Wrap(err, fmt.Errorf("detailed error"))
},
expected: "detailed error",
}, {
message: "annotated wrapped error",
generator: func() error {
err := errors.Errorf("first error")
err = errors.Wrap(err, fmt.Errorf("detailed error"))
return errors.Annotatef(err, "annotated")
},
expected: "annotated: detailed error",
}, {
message: "traced, and annotated",
generator: func() error {
err := errors.New("first error")
err = errors.Trace(err)
err = errors.Annotate(err, "some context")
err = errors.Trace(err)
err = errors.Annotate(err, "more context")
return errors.Trace(err)
},
expected: "more context: some context: first error",
}, {
message: "traced, and annotated, masked and annotated",
generator: func() error {
err := errors.New("first error")
err = errors.Trace(err)
err = errors.Annotate(err, "some context")
err = errors.Maskf(err, "masked")
err = errors.Annotate(err, "more context")
return errors.Trace(err)
},
expected: "more context: masked: some context: first error",
},
} {
c.Logf("%v: %s", i, test.message)
err := test.generator()
ok := c.Check(err.Error(), gc.Equals, test.expected)
if !ok {
c.Logf("%#v", test.generator())
}
}
}
type embed struct {
errors.Err
}
func newEmbed(format string, args ...interface{}) *embed {
err := &embed{errors.NewErr(format, args...)}
err.SetLocation(1)
return err
}
func (*errorsSuite) TestNewErr(c *gc.C) {
if runtime.Compiler == "gccgo" {
c.Skip("gccgo can't determine the location")
}
err := newEmbed("testing %d", 42) //err embedErr
c.Assert(err.Error(), gc.Equals, "testing 42")
c.Assert(errors.Cause(err), gc.Equals, err)
c.Assert(errors.Details(err), jc.Contains, tagToLocation["embedErr"].String())
}
var _ error = (*embed)(nil)
// This is an uncomparable error type, as it is a struct that supports the
// error interface (as opposed to a pointer type).
type error_ struct {
info string
slice []string
}
// Create a non-comparable error
func newNonComparableError(message string) error {
return error_{info: message}
}
func (e error_) Error() string {
return e.info
}
func newError(message string) error {
return testError{message}
}
// The testError is a value type error for ease of seeing results
// when the test fails.
type testError struct {
message string
}
func (e testError) Error() string {
return e.message
}

View File

@ -1,235 +0,0 @@
// Copyright 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package errors
import (
"fmt"
)
// wrap is a helper to construct an *wrapper.
func wrap(err error, format, suffix string, args ...interface{}) Err {
newErr := Err{
message: fmt.Sprintf(format+suffix, args...),
previous: err,
}
newErr.SetLocation(2)
return newErr
}
// notFound represents an error when something has not been found.
type notFound struct {
Err
}
// NotFoundf returns an error which satisfies IsNotFound().
func NotFoundf(format string, args ...interface{}) error {
return &notFound{wrap(nil, format, " not found", args...)}
}
// NewNotFound returns an error which wraps err that satisfies
// IsNotFound().
func NewNotFound(err error, msg string) error {
return &notFound{wrap(err, msg, "")}
}
// IsNotFound reports whether err was created with NotFoundf() or
// NewNotFound().
func IsNotFound(err error) bool {
err = Cause(err)
_, ok := err.(*notFound)
return ok
}
// userNotFound represents an error when an inexistent user is looked up.
type userNotFound struct {
Err
}
// UserNotFoundf returns an error which satisfies IsUserNotFound().
func UserNotFoundf(format string, args ...interface{}) error {
return &userNotFound{wrap(nil, format, " user not found", args...)}
}
// NewUserNotFound returns an error which wraps err and satisfies
// IsUserNotFound().
func NewUserNotFound(err error, msg string) error {
return &userNotFound{wrap(err, msg, "")}
}
// IsUserNotFound reports whether err was created with UserNotFoundf() or
// NewUserNotFound().
func IsUserNotFound(err error) bool {
err = Cause(err)
_, ok := err.(*userNotFound)
return ok
}
// unauthorized represents an error when an operation is unauthorized.
type unauthorized struct {
Err
}
// Unauthorizedf returns an error which satisfies IsUnauthorized().
func Unauthorizedf(format string, args ...interface{}) error {
return &unauthorized{wrap(nil, format, "", args...)}
}
// NewUnauthorized returns an error which wraps err and satisfies
// IsUnauthorized().
func NewUnauthorized(err error, msg string) error {
return &unauthorized{wrap(err, msg, "")}
}
// IsUnauthorized reports whether err was created with Unauthorizedf() or
// NewUnauthorized().
func IsUnauthorized(err error) bool {
err = Cause(err)
_, ok := err.(*unauthorized)
return ok
}
// notImplemented represents an error when something is not
// implemented.
type notImplemented struct {
Err
}
// NotImplementedf returns an error which satisfies IsNotImplemented().
func NotImplementedf(format string, args ...interface{}) error {
return &notImplemented{wrap(nil, format, " not implemented", args...)}
}
// NewNotImplemented returns an error which wraps err and satisfies
// IsNotImplemented().
func NewNotImplemented(err error, msg string) error {
return &notImplemented{wrap(err, msg, "")}
}
// IsNotImplemented reports whether err was created with
// NotImplementedf() or NewNotImplemented().
func IsNotImplemented(err error) bool {
err = Cause(err)
_, ok := err.(*notImplemented)
return ok
}
// alreadyExists represents and error when something already exists.
type alreadyExists struct {
Err
}
// AlreadyExistsf returns an error which satisfies IsAlreadyExists().
func AlreadyExistsf(format string, args ...interface{}) error {
return &alreadyExists{wrap(nil, format, " already exists", args...)}
}
// NewAlreadyExists returns an error which wraps err and satisfies
// IsAlreadyExists().
func NewAlreadyExists(err error, msg string) error {
return &alreadyExists{wrap(err, msg, "")}
}
// IsAlreadyExists reports whether the error was created with
// AlreadyExistsf() or NewAlreadyExists().
func IsAlreadyExists(err error) bool {
err = Cause(err)
_, ok := err.(*alreadyExists)
return ok
}
// notSupported represents an error when something is not supported.
type notSupported struct {
Err
}
// NotSupportedf returns an error which satisfies IsNotSupported().
func NotSupportedf(format string, args ...interface{}) error {
return &notSupported{wrap(nil, format, " not supported", args...)}
}
// NewNotSupported returns an error which wraps err and satisfies
// IsNotSupported().
func NewNotSupported(err error, msg string) error {
return &notSupported{wrap(err, msg, "")}
}
// IsNotSupported reports whether the error was created with
// NotSupportedf() or NewNotSupported().
func IsNotSupported(err error) bool {
err = Cause(err)
_, ok := err.(*notSupported)
return ok
}
// notValid represents an error when something is not valid.
type notValid struct {
Err
}
// NotValidf returns an error which satisfies IsNotValid().
func NotValidf(format string, args ...interface{}) error {
return &notValid{wrap(nil, format, " not valid", args...)}
}
// NewNotValid returns an error which wraps err and satisfies IsNotValid().
func NewNotValid(err error, msg string) error {
return &notValid{wrap(err, msg, "")}
}
// IsNotValid reports whether the error was created with NotValidf() or
// NewNotValid().
func IsNotValid(err error) bool {
err = Cause(err)
_, ok := err.(*notValid)
return ok
}
// notProvisioned represents an error when something is not yet provisioned.
type notProvisioned struct {
Err
}
// NotProvisionedf returns an error which satisfies IsNotProvisioned().
func NotProvisionedf(format string, args ...interface{}) error {
return &notProvisioned{wrap(nil, format, " not provisioned", args...)}
}
// NewNotProvisioned returns an error which wraps err that satisfies
// IsNotProvisioned().
func NewNotProvisioned(err error, msg string) error {
return &notProvisioned{wrap(err, msg, "")}
}
// IsNotProvisioned reports whether err was created with NotProvisionedf() or
// NewNotProvisioned().
func IsNotProvisioned(err error) bool {
err = Cause(err)
_, ok := err.(*notProvisioned)
return ok
}
// notAssigned represents an error when something is not yet assigned to
// something else.
type notAssigned struct {
Err
}
// NotAssignedf returns an error which satisfies IsNotAssigned().
func NotAssignedf(format string, args ...interface{}) error {
return &notAssigned{wrap(nil, format, " not assigned", args...)}
}
// NewNotAssigned returns an error which wraps err that satisfies
// IsNotAssigned().
func NewNotAssigned(err error, msg string) error {
return &notAssigned{wrap(err, msg, "")}
}
// IsNotAssigned reports whether err was created with NotAssignedf() or
// NewNotAssigned().
func IsNotAssigned(err error) bool {
err = Cause(err)
_, ok := err.(*notAssigned)
return ok
}

View File

@ -1,171 +0,0 @@
// Copyright 2013, 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package errors_test
import (
stderrors "errors"
"fmt"
"reflect"
"runtime"
"github.com/juju/errors"
jc "github.com/juju/testing/checkers"
gc "gopkg.in/check.v1"
)
// errorInfo holds information about a single error type: a satisfier
// function, wrapping and variable arguments constructors and message
// suffix.
type errorInfo struct {
satisfier func(error) bool
argsConstructor func(string, ...interface{}) error
wrapConstructor func(error, string) error
suffix string
}
// allErrors holds information for all defined errors. When adding new
// errors, add them here as well to include them in tests.
var allErrors = []*errorInfo{
&errorInfo{errors.IsNotFound, errors.NotFoundf, errors.NewNotFound, " not found"},
&errorInfo{errors.IsUserNotFound, errors.UserNotFoundf, errors.NewUserNotFound, " user not found"},
&errorInfo{errors.IsUnauthorized, errors.Unauthorizedf, errors.NewUnauthorized, ""},
&errorInfo{errors.IsNotImplemented, errors.NotImplementedf, errors.NewNotImplemented, " not implemented"},
&errorInfo{errors.IsAlreadyExists, errors.AlreadyExistsf, errors.NewAlreadyExists, " already exists"},
&errorInfo{errors.IsNotSupported, errors.NotSupportedf, errors.NewNotSupported, " not supported"},
&errorInfo{errors.IsNotValid, errors.NotValidf, errors.NewNotValid, " not valid"},
&errorInfo{errors.IsNotProvisioned, errors.NotProvisionedf, errors.NewNotProvisioned, " not provisioned"},
&errorInfo{errors.IsNotAssigned, errors.NotAssignedf, errors.NewNotAssigned, " not assigned"},
}
type errorTypeSuite struct{}
var _ = gc.Suite(&errorTypeSuite{})
func (t *errorInfo) satisfierName() string {
value := reflect.ValueOf(t.satisfier)
f := runtime.FuncForPC(value.Pointer())
return f.Name()
}
func (t *errorInfo) equal(t0 *errorInfo) bool {
if t0 == nil {
return false
}
return t.satisfierName() == t0.satisfierName()
}
type errorTest struct {
err error
message string
errInfo *errorInfo
}
func deferredAnnotatef(err error, format string, args ...interface{}) error {
errors.DeferredAnnotatef(&err, format, args...)
return err
}
func mustSatisfy(c *gc.C, err error, errInfo *errorInfo) {
if errInfo != nil {
msg := fmt.Sprintf("%#v must satisfy %v", err, errInfo.satisfierName())
c.Check(err, jc.Satisfies, errInfo.satisfier, gc.Commentf(msg))
}
}
func mustNotSatisfy(c *gc.C, err error, errInfo *errorInfo) {
if errInfo != nil {
msg := fmt.Sprintf("%#v must not satisfy %v", err, errInfo.satisfierName())
c.Check(err, gc.Not(jc.Satisfies), errInfo.satisfier, gc.Commentf(msg))
}
}
func checkErrorMatches(c *gc.C, err error, message string, errInfo *errorInfo) {
if message == "<nil>" {
c.Check(err, gc.IsNil)
c.Check(errInfo, gc.IsNil)
} else {
c.Check(err, gc.ErrorMatches, message)
}
}
func runErrorTests(c *gc.C, errorTests []errorTest, checkMustSatisfy bool) {
for i, t := range errorTests {
c.Logf("test %d: %T: %v", i, t.err, t.err)
checkErrorMatches(c, t.err, t.message, t.errInfo)
if checkMustSatisfy {
mustSatisfy(c, t.err, t.errInfo)
}
// Check all other satisfiers to make sure none match.
for _, otherErrInfo := range allErrors {
if checkMustSatisfy && otherErrInfo.equal(t.errInfo) {
continue
}
mustNotSatisfy(c, t.err, otherErrInfo)
}
}
}
func (*errorTypeSuite) TestDeferredAnnotatef(c *gc.C) {
// Ensure DeferredAnnotatef annotates the errors.
errorTests := []errorTest{}
for _, errInfo := range allErrors {
errorTests = append(errorTests, []errorTest{{
deferredAnnotatef(nil, "comment"),
"<nil>",
nil,
}, {
deferredAnnotatef(stderrors.New("blast"), "comment"),
"comment: blast",
nil,
}, {
deferredAnnotatef(errInfo.argsConstructor("foo %d", 42), "comment %d", 69),
"comment 69: foo 42" + errInfo.suffix,
errInfo,
}, {
deferredAnnotatef(errInfo.argsConstructor(""), "comment"),
"comment: " + errInfo.suffix,
errInfo,
}, {
deferredAnnotatef(errInfo.wrapConstructor(stderrors.New("pow!"), "woo"), "comment"),
"comment: woo: pow!",
errInfo,
}}...)
}
runErrorTests(c, errorTests, true)
}
func (*errorTypeSuite) TestAllErrors(c *gc.C) {
errorTests := []errorTest{}
for _, errInfo := range allErrors {
errorTests = append(errorTests, []errorTest{{
nil,
"<nil>",
nil,
}, {
errInfo.argsConstructor("foo %d", 42),
"foo 42" + errInfo.suffix,
errInfo,
}, {
errInfo.argsConstructor(""),
errInfo.suffix,
errInfo,
}, {
errInfo.wrapConstructor(stderrors.New("pow!"), "prefix"),
"prefix: pow!",
errInfo,
}, {
errInfo.wrapConstructor(stderrors.New("pow!"), ""),
"pow!",
errInfo,
}, {
errInfo.wrapConstructor(nil, "prefix"),
"prefix",
errInfo,
}}...)
}
runErrorTests(c, errorTests, true)
}

View File

@ -1,23 +0,0 @@
// Copyright 2013, 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package errors_test
import (
"fmt"
"github.com/juju/errors"
)
func ExampleTrace() {
var err1 error = fmt.Errorf("something wicked this way comes")
var err2 error = nil
// Tracing a non nil error will return an error
fmt.Println(errors.Trace(err1))
// Tracing nil will return nil
fmt.Println(errors.Trace(err2))
// Output: something wicked this way comes
// <nil>
}

View File

@ -1,12 +0,0 @@
// Copyright 2013, 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package errors
// Since variables are declared before the init block, in order to get the goPath
// we need to return it rather than just reference it.
func GoPath() string {
return goPath
}
var TrimGoPath = trimGoPath

View File

@ -1,330 +0,0 @@
// Copyright 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package errors
import (
"fmt"
"strings"
)
// New is a drop in replacement for the standard libary errors module that records
// the location that the error is created.
//
// For example:
// return errors.New("validation failed")
//
func New(message string) error {
err := &Err{message: message}
err.SetLocation(1)
return err
}
// Errorf creates a new annotated error and records the location that the
// error is created. This should be a drop in replacement for fmt.Errorf.
//
// For example:
// return errors.Errorf("validation failed: %s", message)
//
func Errorf(format string, args ...interface{}) error {
err := &Err{message: fmt.Sprintf(format, args...)}
err.SetLocation(1)
return err
}
// Trace adds the location of the Trace call to the stack. The Cause of the
// resulting error is the same as the error parameter. If the other error is
// nil, the result will be nil.
//
// For example:
// if err := SomeFunc(); err != nil {
// return errors.Trace(err)
// }
//
func Trace(other error) error {
if other == nil {
return nil
}
err := &Err{previous: other, cause: Cause(other)}
err.SetLocation(1)
return err
}
// Annotate is used to add extra context to an existing error. The location of
// the Annotate call is recorded with the annotations. The file, line and
// function are also recorded.
//
// For example:
// if err := SomeFunc(); err != nil {
// return errors.Annotate(err, "failed to frombulate")
// }
//
func Annotate(other error, message string) error {
if other == nil {
return nil
}
err := &Err{
previous: other,
cause: Cause(other),
message: message,
}
err.SetLocation(1)
return err
}
// Annotatef is used to add extra context to an existing error. The location of
// the Annotate call is recorded with the annotations. The file, line and
// function are also recorded.
//
// For example:
// if err := SomeFunc(); err != nil {
// return errors.Annotatef(err, "failed to frombulate the %s", arg)
// }
//
func Annotatef(other error, format string, args ...interface{}) error {
if other == nil {
return nil
}
err := &Err{
previous: other,
cause: Cause(other),
message: fmt.Sprintf(format, args...),
}
err.SetLocation(1)
return err
}
// DeferredAnnotatef annotates the given error (when it is not nil) with the given
// format string and arguments (like fmt.Sprintf). If *err is nil, DeferredAnnotatef
// does nothing. This method is used in a defer statement in order to annotate any
// resulting error with the same message.
//
// For example:
//
// defer DeferredAnnotatef(&err, "failed to frombulate the %s", arg)
//
func DeferredAnnotatef(err *error, format string, args ...interface{}) {
if *err == nil {
return
}
newErr := &Err{
message: fmt.Sprintf(format, args...),
cause: Cause(*err),
previous: *err,
}
newErr.SetLocation(1)
*err = newErr
}
// Wrap changes the Cause of the error. The location of the Wrap call is also
// stored in the error stack.
//
// For example:
// if err := SomeFunc(); err != nil {
// newErr := &packageError{"more context", private_value}
// return errors.Wrap(err, newErr)
// }
//
func Wrap(other, newDescriptive error) error {
err := &Err{
previous: other,
cause: newDescriptive,
}
err.SetLocation(1)
return err
}
// Wrapf changes the Cause of the error, and adds an annotation. The location
// of the Wrap call is also stored in the error stack.
//
// For example:
// if err := SomeFunc(); err != nil {
// return errors.Wrapf(err, simpleErrorType, "invalid value %q", value)
// }
//
func Wrapf(other, newDescriptive error, format string, args ...interface{}) error {
err := &Err{
message: fmt.Sprintf(format, args...),
previous: other,
cause: newDescriptive,
}
err.SetLocation(1)
return err
}
// Mask masks the given error with the given format string and arguments (like
// fmt.Sprintf), returning a new error that maintains the error stack, but
// hides the underlying error type. The error string still contains the full
// annotations. If you want to hide the annotations, call Wrap.
func Maskf(other error, format string, args ...interface{}) error {
if other == nil {
return nil
}
err := &Err{
message: fmt.Sprintf(format, args...),
previous: other,
}
err.SetLocation(1)
return err
}
// Mask hides the underlying error type, and records the location of the masking.
func Mask(other error) error {
if other == nil {
return nil
}
err := &Err{
previous: other,
}
err.SetLocation(1)
return err
}
// Cause returns the cause of the given error. This will be either the
// original error, or the result of a Wrap or Mask call.
//
// Cause is the usual way to diagnose errors that may have been wrapped by
// the other errors functions.
func Cause(err error) error {
var diag error
if err, ok := err.(causer); ok {
diag = err.Cause()
}
if diag != nil {
return diag
}
return err
}
type causer interface {
Cause() error
}
type wrapper interface {
// Message returns the top level error message,
// not including the message from the Previous
// error.
Message() string
// Underlying returns the Previous error, or nil
// if there is none.
Underlying() error
}
type locationer interface {
Location() (string, int)
}
var (
_ wrapper = (*Err)(nil)
_ locationer = (*Err)(nil)
_ causer = (*Err)(nil)
)
// Details returns information about the stack of errors wrapped by err, in
// the format:
//
// [{filename:99: error one} {otherfile:55: cause of error one}]
//
// This is a terse alternative to ErrorStack as it returns a single line.
func Details(err error) string {
if err == nil {
return "[]"
}
var s []byte
s = append(s, '[')
for {
s = append(s, '{')
if err, ok := err.(locationer); ok {
file, line := err.Location()
if file != "" {
s = append(s, fmt.Sprintf("%s:%d", file, line)...)
s = append(s, ": "...)
}
}
if cerr, ok := err.(wrapper); ok {
s = append(s, cerr.Message()...)
err = cerr.Underlying()
} else {
s = append(s, err.Error()...)
err = nil
}
s = append(s, '}')
if err == nil {
break
}
s = append(s, ' ')
}
s = append(s, ']')
return string(s)
}
// ErrorStack returns a string representation of the annotated error. If the
// error passed as the parameter is not an annotated error, the result is
// simply the result of the Error() method on that error.
//
// If the error is an annotated error, a multi-line string is returned where
// each line represents one entry in the annotation stack. The full filename
// from the call stack is used in the output.
//
// first error
// github.com/juju/errors/annotation_test.go:193:
// github.com/juju/errors/annotation_test.go:194: annotation
// github.com/juju/errors/annotation_test.go:195:
// github.com/juju/errors/annotation_test.go:196: more context
// github.com/juju/errors/annotation_test.go:197:
func ErrorStack(err error) string {
return strings.Join(errorStack(err), "\n")
}
func errorStack(err error) []string {
if err == nil {
return nil
}
// We want the first error first
var lines []string
for {
var buff []byte
if err, ok := err.(locationer); ok {
file, line := err.Location()
// Strip off the leading GOPATH/src path elements.
file = trimGoPath(file)
if file != "" {
buff = append(buff, fmt.Sprintf("%s:%d", file, line)...)
buff = append(buff, ": "...)
}
}
if cerr, ok := err.(wrapper); ok {
message := cerr.Message()
buff = append(buff, message...)
// If there is a cause for this error, and it is different to the cause
// of the underlying error, then output the error string in the stack trace.
var cause error
if err1, ok := err.(causer); ok {
cause = err1.Cause()
}
err = cerr.Underlying()
if cause != nil && !sameError(Cause(err), cause) {
if message != "" {
buff = append(buff, ": "...)
}
buff = append(buff, cause.Error()...)
}
} else {
buff = append(buff, err.Error()...)
err = nil
}
lines = append(lines, string(buff))
if err == nil {
break
}
}
// reverse the lines to get the original error, which was at the end of
// the list, back to the start.
var result []string
for i := len(lines); i > 0; i-- {
result = append(result, lines[i-1])
}
return result
}

View File

@ -1,305 +0,0 @@
// Copyright 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package errors_test
import (
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
jc "github.com/juju/testing/checkers"
gc "gopkg.in/check.v1"
"github.com/juju/errors"
)
type functionSuite struct {
}
var _ = gc.Suite(&functionSuite{})
func (*functionSuite) TestNew(c *gc.C) {
err := errors.New("testing") //err newTest
c.Assert(err.Error(), gc.Equals, "testing")
c.Assert(errors.Cause(err), gc.Equals, err)
c.Assert(errors.Details(err), jc.Contains, tagToLocation["newTest"].String())
}
func (*functionSuite) TestErrorf(c *gc.C) {
err := errors.Errorf("testing %d", 42) //err errorfTest
c.Assert(err.Error(), gc.Equals, "testing 42")
c.Assert(errors.Cause(err), gc.Equals, err)
c.Assert(errors.Details(err), jc.Contains, tagToLocation["errorfTest"].String())
}
func (*functionSuite) TestTrace(c *gc.C) {
first := errors.New("first")
err := errors.Trace(first) //err traceTest
c.Assert(err.Error(), gc.Equals, "first")
c.Assert(errors.Cause(err), gc.Equals, first)
c.Assert(errors.Details(err), jc.Contains, tagToLocation["traceTest"].String())
c.Assert(errors.Trace(nil), gc.IsNil)
}
func (*functionSuite) TestAnnotate(c *gc.C) {
first := errors.New("first")
err := errors.Annotate(first, "annotation") //err annotateTest
c.Assert(err.Error(), gc.Equals, "annotation: first")
c.Assert(errors.Cause(err), gc.Equals, first)
c.Assert(errors.Details(err), jc.Contains, tagToLocation["annotateTest"].String())
c.Assert(errors.Annotate(nil, "annotate"), gc.IsNil)
}
func (*functionSuite) TestAnnotatef(c *gc.C) {
first := errors.New("first")
err := errors.Annotatef(first, "annotation %d", 2) //err annotatefTest
c.Assert(err.Error(), gc.Equals, "annotation 2: first")
c.Assert(errors.Cause(err), gc.Equals, first)
c.Assert(errors.Details(err), jc.Contains, tagToLocation["annotatefTest"].String())
c.Assert(errors.Annotatef(nil, "annotate"), gc.IsNil)
}
func (*functionSuite) TestDeferredAnnotatef(c *gc.C) {
// NOTE: this test fails with gccgo
if runtime.Compiler == "gccgo" {
c.Skip("gccgo can't determine the location")
}
first := errors.New("first")
test := func() (err error) {
defer errors.DeferredAnnotatef(&err, "deferred %s", "annotate")
return first
} //err deferredAnnotate
err := test()
c.Assert(err.Error(), gc.Equals, "deferred annotate: first")
c.Assert(errors.Cause(err), gc.Equals, first)
c.Assert(errors.Details(err), jc.Contains, tagToLocation["deferredAnnotate"].String())
err = nil
errors.DeferredAnnotatef(&err, "deferred %s", "annotate")
c.Assert(err, gc.IsNil)
}
func (*functionSuite) TestWrap(c *gc.C) {
first := errors.New("first") //err wrapFirst
detailed := errors.New("detailed")
err := errors.Wrap(first, detailed) //err wrapTest
c.Assert(err.Error(), gc.Equals, "detailed")
c.Assert(errors.Cause(err), gc.Equals, detailed)
c.Assert(errors.Details(err), jc.Contains, tagToLocation["wrapFirst"].String())
c.Assert(errors.Details(err), jc.Contains, tagToLocation["wrapTest"].String())
}
func (*functionSuite) TestWrapOfNil(c *gc.C) {
detailed := errors.New("detailed")
err := errors.Wrap(nil, detailed) //err nilWrapTest
c.Assert(err.Error(), gc.Equals, "detailed")
c.Assert(errors.Cause(err), gc.Equals, detailed)
c.Assert(errors.Details(err), jc.Contains, tagToLocation["nilWrapTest"].String())
}
func (*functionSuite) TestWrapf(c *gc.C) {
first := errors.New("first") //err wrapfFirst
detailed := errors.New("detailed")
err := errors.Wrapf(first, detailed, "value %d", 42) //err wrapfTest
c.Assert(err.Error(), gc.Equals, "value 42: detailed")
c.Assert(errors.Cause(err), gc.Equals, detailed)
c.Assert(errors.Details(err), jc.Contains, tagToLocation["wrapfFirst"].String())
c.Assert(errors.Details(err), jc.Contains, tagToLocation["wrapfTest"].String())
}
func (*functionSuite) TestWrapfOfNil(c *gc.C) {
detailed := errors.New("detailed")
err := errors.Wrapf(nil, detailed, "value %d", 42) //err nilWrapfTest
c.Assert(err.Error(), gc.Equals, "value 42: detailed")
c.Assert(errors.Cause(err), gc.Equals, detailed)
c.Assert(errors.Details(err), jc.Contains, tagToLocation["nilWrapfTest"].String())
}
func (*functionSuite) TestMask(c *gc.C) {
first := errors.New("first")
err := errors.Mask(first) //err maskTest
c.Assert(err.Error(), gc.Equals, "first")
c.Assert(errors.Cause(err), gc.Equals, err)
c.Assert(errors.Details(err), jc.Contains, tagToLocation["maskTest"].String())
c.Assert(errors.Mask(nil), gc.IsNil)
}
func (*functionSuite) TestMaskf(c *gc.C) {
first := errors.New("first")
err := errors.Maskf(first, "masked %d", 42) //err maskfTest
c.Assert(err.Error(), gc.Equals, "masked 42: first")
c.Assert(errors.Cause(err), gc.Equals, err)
c.Assert(errors.Details(err), jc.Contains, tagToLocation["maskfTest"].String())
c.Assert(errors.Maskf(nil, "mask"), gc.IsNil)
}
func (*functionSuite) TestCause(c *gc.C) {
c.Assert(errors.Cause(nil), gc.IsNil)
c.Assert(errors.Cause(someErr), gc.Equals, someErr)
fmtErr := fmt.Errorf("simple")
c.Assert(errors.Cause(fmtErr), gc.Equals, fmtErr)
err := errors.Wrap(someErr, fmtErr)
c.Assert(errors.Cause(err), gc.Equals, fmtErr)
err = errors.Annotate(err, "annotated")
c.Assert(errors.Cause(err), gc.Equals, fmtErr)
err = errors.Maskf(err, "maksed")
c.Assert(errors.Cause(err), gc.Equals, err)
// Look for a file that we know isn't there.
dir := c.MkDir()
_, err = os.Stat(filepath.Join(dir, "not-there"))
c.Assert(os.IsNotExist(err), jc.IsTrue)
err = errors.Annotatef(err, "wrap it")
// Now the error itself isn't a 'IsNotExist'.
c.Assert(os.IsNotExist(err), jc.IsFalse)
// However if we use the Check method, it is.
c.Assert(os.IsNotExist(errors.Cause(err)), jc.IsTrue)
}
func (s *functionSuite) TestDetails(c *gc.C) {
if runtime.Compiler == "gccgo" {
c.Skip("gccgo can't determine the location")
}
c.Assert(errors.Details(nil), gc.Equals, "[]")
otherErr := fmt.Errorf("other")
checkDetails(c, otherErr, "[{other}]")
err0 := newEmbed("foo") //err TestStack#0
checkDetails(c, err0, "[{$TestStack#0$: foo}]")
err1 := errors.Annotate(err0, "bar") //err TestStack#1
checkDetails(c, err1, "[{$TestStack#1$: bar} {$TestStack#0$: foo}]")
err2 := errors.Trace(err1) //err TestStack#2
checkDetails(c, err2, "[{$TestStack#2$: } {$TestStack#1$: bar} {$TestStack#0$: foo}]")
}
type tracer interface {
StackTrace() []string
}
func (*functionSuite) TestErrorStack(c *gc.C) {
for i, test := range []struct {
message string
generator func() error
expected string
tracer bool
}{
{
message: "nil",
generator: func() error {
return nil
},
}, {
message: "raw error",
generator: func() error {
return fmt.Errorf("raw")
},
expected: "raw",
}, {
message: "single error stack",
generator: func() error {
return errors.New("first error") //err single
},
expected: "$single$: first error",
tracer: true,
}, {
message: "annotated error",
generator: func() error {
err := errors.New("first error") //err annotated-0
return errors.Annotate(err, "annotation") //err annotated-1
},
expected: "" +
"$annotated-0$: first error\n" +
"$annotated-1$: annotation",
tracer: true,
}, {
message: "wrapped error",
generator: func() error {
err := errors.New("first error") //err wrapped-0
return errors.Wrap(err, newError("detailed error")) //err wrapped-1
},
expected: "" +
"$wrapped-0$: first error\n" +
"$wrapped-1$: detailed error",
tracer: true,
}, {
message: "annotated wrapped error",
generator: func() error {
err := errors.Errorf("first error") //err ann-wrap-0
err = errors.Wrap(err, fmt.Errorf("detailed error")) //err ann-wrap-1
return errors.Annotatef(err, "annotated") //err ann-wrap-2
},
expected: "" +
"$ann-wrap-0$: first error\n" +
"$ann-wrap-1$: detailed error\n" +
"$ann-wrap-2$: annotated",
tracer: true,
}, {
message: "traced, and annotated",
generator: func() error {
err := errors.New("first error") //err stack-0
err = errors.Trace(err) //err stack-1
err = errors.Annotate(err, "some context") //err stack-2
err = errors.Trace(err) //err stack-3
err = errors.Annotate(err, "more context") //err stack-4
return errors.Trace(err) //err stack-5
},
expected: "" +
"$stack-0$: first error\n" +
"$stack-1$: \n" +
"$stack-2$: some context\n" +
"$stack-3$: \n" +
"$stack-4$: more context\n" +
"$stack-5$: ",
tracer: true,
}, {
message: "uncomparable, wrapped with a value error",
generator: func() error {
err := newNonComparableError("first error") //err mixed-0
err = errors.Trace(err) //err mixed-1
err = errors.Wrap(err, newError("value error")) //err mixed-2
err = errors.Maskf(err, "masked") //err mixed-3
err = errors.Annotate(err, "more context") //err mixed-4
return errors.Trace(err) //err mixed-5
},
expected: "" +
"first error\n" +
"$mixed-1$: \n" +
"$mixed-2$: value error\n" +
"$mixed-3$: masked\n" +
"$mixed-4$: more context\n" +
"$mixed-5$: ",
tracer: true,
},
} {
c.Logf("%v: %s", i, test.message)
err := test.generator()
expected := replaceLocations(test.expected)
stack := errors.ErrorStack(err)
ok := c.Check(stack, gc.Equals, expected)
if !ok {
c.Logf("%#v", err)
}
tracer, ok := err.(tracer)
c.Check(ok, gc.Equals, test.tracer)
if ok {
stackTrace := tracer.StackTrace()
c.Check(stackTrace, gc.DeepEquals, strings.Split(stack, "\n"))
}
}
}

View File

@ -1,95 +0,0 @@
// Copyright 2013, 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package errors_test
import (
"fmt"
"io/ioutil"
"strings"
"testing"
gc "gopkg.in/check.v1"
"github.com/juju/errors"
)
func Test(t *testing.T) {
gc.TestingT(t)
}
func checkDetails(c *gc.C, err error, details string) {
c.Assert(err, gc.NotNil)
expectedDetails := replaceLocations(details)
c.Assert(errors.Details(err), gc.Equals, expectedDetails)
}
func checkErr(c *gc.C, err, cause error, msg string, details string) {
c.Assert(err, gc.NotNil)
c.Assert(err.Error(), gc.Equals, msg)
c.Assert(errors.Cause(err), gc.Equals, cause)
expectedDetails := replaceLocations(details)
c.Assert(errors.Details(err), gc.Equals, expectedDetails)
}
func replaceLocations(line string) string {
result := ""
for {
i := strings.Index(line, "$")
if i == -1 {
break
}
result += line[0:i]
line = line[i+1:]
i = strings.Index(line, "$")
if i == -1 {
panic("no second $")
}
result += location(line[0:i]).String()
line = line[i+1:]
}
result += line
return result
}
func location(tag string) Location {
loc, ok := tagToLocation[tag]
if !ok {
panic(fmt.Sprintf("tag %q not found", tag))
}
return loc
}
type Location struct {
file string
line int
}
func (loc Location) String() string {
return fmt.Sprintf("%s:%d", loc.file, loc.line)
}
var tagToLocation = make(map[string]Location)
func setLocationsForErrorTags(filename string) {
data, err := ioutil.ReadFile(filename)
if err != nil {
panic(err)
}
filename = "github.com/juju/errors/" + filename
lines := strings.Split(string(data), "\n")
for i, line := range lines {
if j := strings.Index(line, "//err "); j >= 0 {
tag := line[j+len("//err "):]
if _, found := tagToLocation[tag]; found {
panic(fmt.Sprintf("tag %q already processed previously", tag))
}
tagToLocation[tag] = Location{file: filename, line: i + 1}
}
}
}
func init() {
setLocationsForErrorTags("error_test.go")
setLocationsForErrorTags("functions_test.go")
}

View File

@ -1,35 +0,0 @@
// Copyright 2013, 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package errors
import (
"runtime"
"strings"
)
// prefixSize is used internally to trim the user specific path from the
// front of the returned filenames from the runtime call stack.
var prefixSize int
// goPath is the deduced path based on the location of this file as compiled.
var goPath string
func init() {
_, file, _, ok := runtime.Caller(0)
if ok {
// We know that the end of the file should be:
// github.com/juju/errors/path.go
size := len(file)
suffix := len("github.com/juju/errors/path.go")
goPath = file[:size-suffix]
prefixSize = len(goPath)
}
}
func trimGoPath(filename string) string {
if strings.HasPrefix(filename, goPath) {
return filename[prefixSize:]
}
return filename
}

View File

@ -1,29 +0,0 @@
// Copyright 2013, 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package errors_test
import (
"path"
gc "gopkg.in/check.v1"
"github.com/juju/errors"
)
type pathSuite struct{}
var _ = gc.Suite(&pathSuite{})
func (*pathSuite) TestGoPathSet(c *gc.C) {
c.Assert(errors.GoPath(), gc.Not(gc.Equals), "")
}
func (*pathSuite) TestTrimGoPath(c *gc.C) {
relativeImport := "github.com/foo/bar/baz.go"
filename := path.Join(errors.GoPath(), relativeImport)
c.Assert(errors.TrimGoPath(filename), gc.Equals, relativeImport)
absoluteImport := "/usr/share/foo/bar/baz.go"
c.Assert(errors.TrimGoPath(absoluteImport), gc.Equals, absoluteImport)
}