From 661c1e9aa1eba5d8e459fed6f256351b7c04d66a Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Sun, 21 Dec 2014 15:57:41 +0100 Subject: [PATCH 1/4] Restructure backend --- backend/generic.go | 16 ++++++------ backend/generic_test.go | 54 ++++++++++++++++++++++++++++++++++------- backend/id.go | 3 +++ backend/interface.go | 34 ++++++++++++++++++++++---- backend/local.go | 5 ++++ backend/local_test.go | 11 ++++----- 6 files changed, 95 insertions(+), 28 deletions(-) diff --git a/backend/generic.go b/backend/generic.go index 6e09729b3..e5509f334 100644 --- a/backend/generic.go +++ b/backend/generic.go @@ -8,15 +8,12 @@ import ( "errors" "io/ioutil" "sort" - "sync" ) const ( MinPrefixLength = 4 ) -var idPool = sync.Pool{New: func() interface{} { return ID(make([]byte, IDSize)) }} - var ( ErrNoIDPrefixFound = errors.New("no ID found") ErrMultipleIDMatches = errors.New("multiple IDs with prefix found") @@ -24,7 +21,10 @@ var ( // Each lists all entries of type t in the backend and calls function f() with // the id and data. -func Each(be Server, t Type, f func(id ID, data []byte, err error)) error { +func Each(be interface { + lister + getter +}, t Type, f func(id ID, data []byte, err error)) error { ids, err := be.List(t) if err != nil { return err @@ -45,7 +45,7 @@ func Each(be Server, t Type, f func(id ID, data []byte, err error)) error { // Each lists all entries of type t in the backend and calls function f() with // the id. -func EachID(be Server, t Type, f func(ID)) error { +func EachID(be lister, t Type, f func(ID)) error { ids, err := be.List(t) if err != nil { return err @@ -101,7 +101,7 @@ func Hash(data []byte) ID { // Find loads the list of all blobs of type t and searches for IDs which start // with prefix. If none is found, nil and ErrNoIDPrefixFound is returned. If // more than one is found, nil and ErrMultipleIDMatches is returned. -func Find(be Server, t Type, prefix string) (ID, error) { +func Find(be lister, t Type, prefix string) (ID, error) { p, err := hex.DecodeString(prefix) if err != nil { return nil, err @@ -134,7 +134,7 @@ func Find(be Server, t Type, prefix string) (ID, error) { // FindSnapshot takes a string and tries to find a snapshot whose ID matches // the string as closely as possible. -func FindSnapshot(be Server, s string) (ID, error) { +func FindSnapshot(be lister, s string) (ID, error) { // parse ID directly if id, err := ParseID(s); err == nil { return id, nil @@ -151,7 +151,7 @@ func FindSnapshot(be Server, s string) (ID, error) { // PrefixLength returns the number of bytes required so that all prefixes of // all IDs of type t are unique. -func PrefixLength(be Lister, t Type) (int, error) { +func PrefixLength(be lister, t Type) (int, error) { // load all IDs of the given type list, err := be.List(t) if err != nil { diff --git a/backend/generic_test.go b/backend/generic_test.go index 6deb9a07f..2d6aff550 100644 --- a/backend/generic_test.go +++ b/backend/generic_test.go @@ -46,9 +46,40 @@ func str2id(s string) backend.ID { return id } -type IDList backend.IDs +type mockBackend struct { + list func(backend.Type) (backend.IDs, error) + get func(backend.Type, backend.ID) ([]byte, error) + create func(backend.Type, []byte) (backend.ID, error) + test func(backend.Type, backend.ID) (bool, error) + remove func(backend.Type, backend.ID) error + close func() error +} -var samples = IDList{ +func (m mockBackend) List(t backend.Type) (backend.IDs, error) { + return m.list(t) +} + +func (m mockBackend) Get(t backend.Type, id backend.ID) ([]byte, error) { + return m.get(t, id) +} + +func (m mockBackend) Create(t backend.Type, data []byte) (backend.ID, error) { + return m.create(t, data) +} + +func (m mockBackend) Test(t backend.Type, id backend.ID) (bool, error) { + return m.test(t, id) +} + +func (m mockBackend) Remove(t backend.Type, id backend.ID) error { + return m.remove(t, id) +} + +func (m mockBackend) Close() error { + return m.close() +} + +var samples = backend.IDs{ str2id("20bdc1402a6fc9b633aaffffffffffffffffffffffffffffffffffffffffffff"), str2id("20bdc1402a6fc9b633ccd578c4a92d0f4ef1a457fa2e16c596bc73fb409d6cc0"), str2id("20bdc1402a6fc9b633ffffffffffffffffffffffffffffffffffffffffffffff"), @@ -59,20 +90,25 @@ var samples = IDList{ str2id("fa31d65b87affcd167b119e9d3d2a27b8236ca4836cb077ed3e96fcbe209b792"), } -func (l IDList) List(backend.Type) (backend.IDs, error) { - return backend.IDs(l), nil -} - func TestPrefixLength(t *testing.T) { - l, err := backend.PrefixLength(samples, backend.Snapshot) + list := samples + + m := mockBackend{} + m.list = func(t backend.Type) (backend.IDs, error) { + return list, nil + } + + l, err := backend.PrefixLength(m, backend.Snapshot) ok(t, err) equals(t, 10, l) - l, err = backend.PrefixLength(samples[:3], backend.Snapshot) + list = samples[:3] + l, err = backend.PrefixLength(m, backend.Snapshot) ok(t, err) equals(t, 10, l) - l, err = backend.PrefixLength(samples[3:], backend.Snapshot) + list = samples[3:] + l, err = backend.PrefixLength(m, backend.Snapshot) ok(t, err) equals(t, 4, l) } diff --git a/backend/id.go b/backend/id.go index 87071e577..4db643ae2 100644 --- a/backend/id.go +++ b/backend/id.go @@ -6,6 +6,7 @@ import ( "encoding/hex" "encoding/json" "errors" + "sync" ) // IDSize contains the size of an ID, in bytes. @@ -14,6 +15,8 @@ const IDSize = sha256.Size // References content within a repository. type ID []byte +var idPool = sync.Pool{New: func() interface{} { return ID(make([]byte, IDSize)) }} + // ParseID converts the given string to an ID. func ParseID(s string) (ID, error) { b, err := hex.DecodeString(s) diff --git a/backend/interface.go b/backend/interface.go index ec1abb3a3..644ab9653 100644 --- a/backend/interface.go +++ b/backend/interface.go @@ -21,19 +21,43 @@ var ( ErrAlreadyPresent = errors.New("blob is already present in backend") ) -type Lister interface { +type lister interface { List(Type) (IDs, error) } -type Server interface { - Create(Type, []byte) (ID, error) +type getter interface { Get(Type, ID) ([]byte, error) - Lister +} + +type creater interface { + Create(Type, []byte) (ID, error) +} + +type tester interface { Test(Type, ID) (bool, error) +} + +type remover interface { Remove(Type, ID) error - Version() uint +} +type closer interface { Close() error +} +type deleter interface { + Delete() error +} + +type locationer interface { Location() string } + +type backend interface { + lister + getter + creater + tester + remover + closer +} diff --git a/backend/local.go b/backend/local.go index 0a81f1e90..68da2fe74 100644 --- a/backend/local.go +++ b/backend/local.go @@ -328,3 +328,8 @@ func (b *Local) Version() uint { func (b *Local) Close() error { return nil } + +// Delete removes the repository and all files. +func (b *Local) Delete() error { + return os.RemoveAll(b.p) +} diff --git a/backend/local_test.go b/backend/local_test.go index b23dbee5c..e79eca5ef 100644 --- a/backend/local_test.go +++ b/backend/local_test.go @@ -4,7 +4,6 @@ import ( "flag" "fmt" "io/ioutil" - "os" "sort" "testing" @@ -41,10 +40,10 @@ func teardownBackend(t *testing.T, b *backend.Local) { return } - ok(t, os.RemoveAll(b.Location())) + ok(t, b.Delete()) } -func testBackend(b backend.Server, t *testing.T) { +func testBackend(b *backend.Local, t *testing.T) { for _, tpe := range []backend.Type{backend.Data, backend.Key, backend.Lock, backend.Snapshot, backend.Tree, backend.Map} { // detect non-existing files for _, test := range TestStrings { @@ -126,10 +125,10 @@ func TestBackend(t *testing.T) { assert(t, err != nil, "opening invalid repository at /invalid-restic-test should have failed, but err is nil") assert(t, b == nil, fmt.Sprintf("opening invalid repository at /invalid-restic-test should have failed, but b is not nil: %v", b)) - b = setupBackend(t) - defer teardownBackend(t, b) + s := setupBackend(t) + defer teardownBackend(t, s) - testBackend(b, t) + testBackend(s, t) } func TestLocalBackendCreationFailures(t *testing.T) { From cc147c002e64e599d41c266667920a216d2d1ae6 Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Sun, 21 Dec 2014 17:02:49 +0100 Subject: [PATCH 2/4] Introduce type Server --- archiver.go | 8 +-- backend/generic.go | 12 ++--- backend/interface.go | 30 +++++------ cmd/restic/cmd_find.go | 2 +- cmd/restic/cmd_fsck.go | 2 +- cmd/restic/cmd_key.go | 8 +-- cmd/restic/cmd_list.go | 11 ++-- cmd/restic/cmd_ls.go | 2 +- cmd/restic/main.go | 22 ++++---- contenthandler.go | 16 +++--- key.go | 37 ++++---------- key_test.go | 39 +++++++------- restorer.go | 8 +-- server.go | 113 +++++++++++++++++++++++++++++++++++++++++ snapshot_test.go | 2 +- 15 files changed, 206 insertions(+), 106 deletions(-) create mode 100644 server.go diff --git a/archiver.go b/archiver.go index caa66e5a8..aba53cbcb 100644 --- a/archiver.go +++ b/archiver.go @@ -22,7 +22,7 @@ const ( ) type Archiver struct { - be backend.Server + s Server key *Key ch *ContentHandler @@ -58,10 +58,10 @@ func (s *Stats) Add(other Stats) { s.Other += other.Other } -func NewArchiver(be backend.Server, key *Key) (*Archiver, error) { +func NewArchiver(s Server, key *Key) (*Archiver, error) { var err error arch := &Archiver{ - be: be, + s: s, key: key, fileToken: make(chan struct{}, maxConcurrentFiles), blobToken: make(chan struct{}, maxConcurrentBlobs), @@ -82,7 +82,7 @@ func NewArchiver(be backend.Server, key *Key) (*Archiver, error) { arch.Filter = func(string, os.FileInfo) bool { return true } arch.bl = NewBlobList() - arch.ch, err = NewContentHandler(be, key) + arch.ch, err = NewContentHandler(s, key) if err != nil { return nil, err } diff --git a/backend/generic.go b/backend/generic.go index e5509f334..dd5e9ac78 100644 --- a/backend/generic.go +++ b/backend/generic.go @@ -22,8 +22,8 @@ var ( // Each lists all entries of type t in the backend and calls function f() with // the id and data. func Each(be interface { - lister - getter + Lister + Getter }, t Type, f func(id ID, data []byte, err error)) error { ids, err := be.List(t) if err != nil { @@ -45,7 +45,7 @@ func Each(be interface { // Each lists all entries of type t in the backend and calls function f() with // the id. -func EachID(be lister, t Type, f func(ID)) error { +func EachID(be Lister, t Type, f func(ID)) error { ids, err := be.List(t) if err != nil { return err @@ -101,7 +101,7 @@ func Hash(data []byte) ID { // Find loads the list of all blobs of type t and searches for IDs which start // with prefix. If none is found, nil and ErrNoIDPrefixFound is returned. If // more than one is found, nil and ErrMultipleIDMatches is returned. -func Find(be lister, t Type, prefix string) (ID, error) { +func Find(be Lister, t Type, prefix string) (ID, error) { p, err := hex.DecodeString(prefix) if err != nil { return nil, err @@ -134,7 +134,7 @@ func Find(be lister, t Type, prefix string) (ID, error) { // FindSnapshot takes a string and tries to find a snapshot whose ID matches // the string as closely as possible. -func FindSnapshot(be lister, s string) (ID, error) { +func FindSnapshot(be Lister, s string) (ID, error) { // parse ID directly if id, err := ParseID(s); err == nil { return id, nil @@ -151,7 +151,7 @@ func FindSnapshot(be lister, s string) (ID, error) { // PrefixLength returns the number of bytes required so that all prefixes of // all IDs of type t are unique. -func PrefixLength(be lister, t Type) (int, error) { +func PrefixLength(be Lister, t Type) (int, error) { // load all IDs of the given type list, err := be.List(t) if err != nil { diff --git a/backend/interface.go b/backend/interface.go index 644ab9653..b0ada8602 100644 --- a/backend/interface.go +++ b/backend/interface.go @@ -21,43 +21,43 @@ var ( ErrAlreadyPresent = errors.New("blob is already present in backend") ) -type lister interface { +type Lister interface { List(Type) (IDs, error) } -type getter interface { +type Getter interface { Get(Type, ID) ([]byte, error) } -type creater interface { +type Creater interface { Create(Type, []byte) (ID, error) } -type tester interface { +type Tester interface { Test(Type, ID) (bool, error) } -type remover interface { +type Remover interface { Remove(Type, ID) error } -type closer interface { +type Closer interface { Close() error } -type deleter interface { +type Deleter interface { Delete() error } -type locationer interface { +type Locationer interface { Location() string } -type backend interface { - lister - getter - creater - tester - remover - closer +type Backend interface { + Lister + Getter + Creater + Tester + Remover + Closer } diff --git a/cmd/restic/cmd_find.go b/cmd/restic/cmd_find.go index aa8157259..585e60339 100644 --- a/cmd/restic/cmd_find.go +++ b/cmd/restic/cmd_find.go @@ -110,7 +110,7 @@ func (c CmdFind) findInTree(ch *restic.ContentHandler, id backend.ID, path strin return results, nil } -func (c CmdFind) findInSnapshot(be backend.Server, key *restic.Key, id backend.ID) error { +func (c CmdFind) findInSnapshot(be restic.Server, key *restic.Key, id backend.ID) error { debug("searching in snapshot %s\n for entries within [%s %s]", id, c.oldest, c.newest) ch, err := restic.NewContentHandler(be, key) diff --git a/cmd/restic/cmd_fsck.go b/cmd/restic/cmd_fsck.go index 4db9a6ff2..fcb1c6329 100644 --- a/cmd/restic/cmd_fsck.go +++ b/cmd/restic/cmd_fsck.go @@ -75,7 +75,7 @@ func fsckTree(ch *restic.ContentHandler, id backend.ID) error { return nil } -func fsck_snapshot(be backend.Server, key *restic.Key, id backend.ID) error { +func fsck_snapshot(be restic.Server, key *restic.Key, id backend.ID) error { debug("checking snapshot %v\n", id) ch, err := restic.NewContentHandler(be, key) diff --git a/cmd/restic/cmd_key.go b/cmd/restic/cmd_key.go index c111696d1..9332ebb8c 100644 --- a/cmd/restic/cmd_key.go +++ b/cmd/restic/cmd_key.go @@ -22,7 +22,7 @@ func init() { } } -func list_keys(be backend.Server, key *restic.Key) error { +func list_keys(be restic.Server, key *restic.Key) error { tab := NewTable() tab.Header = fmt.Sprintf(" %-10s %-10s %-10s %s", "ID", "User", "Host", "Created") tab.RowFormat = "%s%-10s %-10s %-10s %s" @@ -54,7 +54,7 @@ func list_keys(be backend.Server, key *restic.Key) error { return nil } -func add_key(be backend.Server, key *restic.Key) error { +func add_key(be restic.Server, key *restic.Key) error { pw := readPassword("RESTIC_NEWPASSWORD", "enter password for new key: ") pw2 := readPassword("RESTIC_NEWPASSWORD", "enter password again: ") @@ -72,7 +72,7 @@ func add_key(be backend.Server, key *restic.Key) error { return nil } -func delete_key(be backend.Server, key *restic.Key, id backend.ID) error { +func delete_key(be restic.Server, key *restic.Key, id backend.ID) error { if id.Equal(key.ID()) { return errors.New("refusing to remove key currently used to access repository") } @@ -86,7 +86,7 @@ func delete_key(be backend.Server, key *restic.Key, id backend.ID) error { return nil } -func change_password(be backend.Server, key *restic.Key) error { +func change_password(be restic.Server, key *restic.Key) error { pw := readPassword("RESTIC_NEWPASSWORD", "enter password for new key: ") pw2 := readPassword("RESTIC_NEWPASSWORD", "enter password again: ") diff --git a/cmd/restic/cmd_list.go b/cmd/restic/cmd_list.go index caca10556..b85b9f010 100644 --- a/cmd/restic/cmd_list.go +++ b/cmd/restic/cmd_list.go @@ -3,6 +3,7 @@ package main import ( "errors" "fmt" + "github.com/restic/restic/backend" ) @@ -27,22 +28,22 @@ func (cmd CmdList) Execute(args []string) error { return fmt.Errorf("type not specified, Usage: %s", cmd.Usage()) } - be, key, err := OpenRepo() + be, _, err := OpenRepo() if err != nil { return err } var ( t backend.Type - each func(backend.Server, backend.Type, func(backend.ID, []byte, error)) error = backend.Each + each func(backend.Type, func(backend.ID, []byte, error)) error = be.Each ) switch args[0] { case "data": t = backend.Data - each = key.Each + each = be.EachDecrypted case "trees": t = backend.Tree - each = key.Each + each = be.EachDecrypted case "snapshots": t = backend.Snapshot case "maps": @@ -55,7 +56,7 @@ func (cmd CmdList) Execute(args []string) error { return errors.New("invalid type") } - return each(be, t, func(id backend.ID, data []byte, err error) { + return each(t, func(id backend.ID, data []byte, err error) { if t == backend.Data || t == backend.Tree { fmt.Printf("%s %s\n", id, backend.Hash(data)) } else { diff --git a/cmd/restic/cmd_ls.go b/cmd/restic/cmd_ls.go index a586a3835..b64454a43 100644 --- a/cmd/restic/cmd_ls.go +++ b/cmd/restic/cmd_ls.go @@ -63,7 +63,7 @@ func (cmd CmdLs) Usage() string { return "ls snapshot-ID [DIR]" } -func (cmd CmdLs) Execute(be backend.Server, key *restic.Key, args []string) error { +func (cmd CmdLs) Execute(be restic.Server, key *restic.Key, args []string) error { if len(args) < 1 || len(args) > 2 { return fmt.Errorf("wrong number of arguments, Usage: %s", cmd.Usage()) } diff --git a/cmd/restic/main.go b/cmd/restic/main.go index 469034af7..ee2f835bf 100644 --- a/cmd/restic/main.go +++ b/cmd/restic/main.go @@ -70,13 +70,15 @@ func (cmd CmdInit) Execute(args []string) error { os.Exit(1) } - _, err = restic.CreateKey(be, pw) + s := restic.NewServer(be) + + _, err = restic.CreateKey(s, pw) if err != nil { fmt.Fprintf(os.Stderr, "creating key in backend at %s failed: %v\n", opts.Repo, err) os.Exit(1) } - fmt.Printf("created restic backend at %s\n", be.Location()) + fmt.Printf("created restic backend at %s\n", opts.Repo) return nil } @@ -86,7 +88,7 @@ func (cmd CmdInit) Execute(args []string) error { // * /foo/bar -> local repository at /foo/bar // * sftp://user@host/foo/bar -> remote sftp repository on host for user at path foo/bar // * sftp://host//tmp/backup -> remote sftp repository on host at path /tmp/backup -func open(u string) (backend.Server, error) { +func open(u string) (backend.Backend, error) { url, err := url.Parse(u) if err != nil { return nil, err @@ -107,7 +109,7 @@ func open(u string) (backend.Server, error) { } // Create the backend specified by URI. -func create(u string) (backend.Server, error) { +func create(u string) (backend.Backend, error) { url, err := url.Parse(u) if err != nil { return nil, err @@ -127,18 +129,20 @@ func create(u string) (backend.Server, error) { return backend.CreateSFTP(url.Path[1:], "ssh", args...) } -func OpenRepo() (backend.Server, *restic.Key, error) { +func OpenRepo() (restic.Server, *restic.Key, error) { be, err := open(opts.Repo) if err != nil { - return nil, nil, err + return restic.Server{}, nil, err } - key, err := restic.SearchKey(be, readPassword("RESTIC_PASSWORD", "Enter Password for Repository: ")) + s := restic.NewServer(be) + + key, err := restic.SearchKey(s, readPassword("RESTIC_PASSWORD", "Enter Password for Repository: ")) if err != nil { - return nil, nil, fmt.Errorf("unable to open repo: %v", err) + return restic.Server{}, nil, fmt.Errorf("unable to open repo: %v", err) } - return be, key, nil + return s, key, nil } func init() { diff --git a/contenthandler.go b/contenthandler.go index bf43255d1..3beed5f09 100644 --- a/contenthandler.go +++ b/contenthandler.go @@ -11,16 +11,16 @@ import ( var ErrWrongData = errors.New("wrong data decrypt, checksum does not match") type ContentHandler struct { - be backend.Server + s Server key *Key bl *BlobList } // NewContentHandler creates a new content handler. -func NewContentHandler(be backend.Server, key *Key) (*ContentHandler, error) { +func NewContentHandler(s Server, key *Key) (*ContentHandler, error) { ch := &ContentHandler{ - be: be, + s: s, key: key, bl: NewBlobList(), } @@ -49,7 +49,7 @@ func (ch *ContentHandler) LoadSnapshot(id backend.ID) (*Snapshot, error) { // into the content handler. func (ch *ContentHandler) LoadAllMaps() error { // add all maps from all snapshots that can be decrypted to the storage map - err := backend.EachID(ch.be, backend.Map, func(id backend.ID) { + err := backend.EachID(ch.s, backend.Map, func(id backend.ID) { bl, err := LoadBlobList(ch, id) if err != nil { return @@ -103,7 +103,7 @@ func (ch *ContentHandler) Save(t backend.Type, data []byte) (Blob, error) { ciphertext = ciphertext[:n] // save blob - sid, err := ch.be.Create(t, ciphertext) + sid, err := ch.s.Create(t, ciphertext) if err != nil { return Blob{}, err } @@ -133,7 +133,7 @@ func (ch *ContentHandler) SaveJSON(t backend.Type, item interface{}) (Blob, erro func (ch *ContentHandler) Load(t backend.Type, id backend.ID) ([]byte, error) { if t == backend.Snapshot { // load data - buf, err := ch.be.Get(t, id) + buf, err := ch.s.Get(t, id) if err != nil { return nil, err } @@ -154,7 +154,7 @@ func (ch *ContentHandler) Load(t backend.Type, id backend.ID) ([]byte, error) { } // load data - buf, err := ch.be.Get(t, blob.Storage) + buf, err := ch.s.Get(t, blob.Storage) if err != nil { return nil, err } @@ -201,7 +201,7 @@ func (ch *ContentHandler) LoadJSON(t backend.Type, id backend.ID, item interface // decrypts it and calls json.Unmarshal on the item. func (ch *ContentHandler) LoadJSONRaw(t backend.Type, id backend.ID, item interface{}) error { // load data - buf, err := ch.be.Get(t, id) + buf, err := ch.s.Get(t, id) if err != nil { return err } diff --git a/key.go b/key.go index 4889047de..4bfad1380 100644 --- a/key.go +++ b/key.go @@ -75,7 +75,7 @@ type keys struct { // CreateKey initializes a master key in the given backend and encrypts it with // the password. -func CreateKey(be backend.Server, password string) (*Key, error) { +func CreateKey(s Server, password string) (*Key, error) { // fill meta data about key k := &Key{ Created: time.Now(), @@ -131,7 +131,7 @@ func CreateKey(be backend.Server, password string) (*Key, error) { } // store in repository and return - id, err := be.Create(backend.Key, buf) + id, err := s.Create(backend.Key, buf) if err != nil { return nil, err } @@ -143,9 +143,9 @@ func CreateKey(be backend.Server, password string) (*Key, error) { } // OpenKey tries do decrypt the key specified by id with the given password. -func OpenKey(be backend.Server, id backend.ID, password string) (*Key, error) { +func OpenKey(s Server, id backend.ID, password string) (*Key, error) { // extract data from repo - data, err := be.Get(backend.Key, id) + data, err := s.Get(backend.Key, id) if err != nil { return nil, err } @@ -187,9 +187,9 @@ func OpenKey(be backend.Server, id backend.ID, password string) (*Key, error) { // SearchKey tries to decrypt all keys in the backend with the given password. // If none could be found, ErrNoKeyFound is returned. -func SearchKey(be backend.Server, password string) (*Key, error) { +func SearchKey(s Server, password string) (*Key, error) { // list all keys - ids, err := be.List(backend.Key) + ids, err := s.List(backend.Key) if err != nil { panic(err) } @@ -197,7 +197,7 @@ func SearchKey(be backend.Server, password string) (*Key, error) { // try all keys in repo var key *Key for _, id := range ids { - key, err = OpenKey(be, id, password) + key, err = OpenKey(s, id, password) if err != nil { continue } @@ -209,7 +209,7 @@ func SearchKey(be backend.Server, password string) (*Key, error) { } // AddKey adds a new key to an already existing repository. -func (oldkey *Key) AddKey(be backend.Server, password string) (backend.ID, error) { +func (oldkey *Key) AddKey(s Server, password string) (backend.ID, error) { // fill meta data about key newkey := &Key{ Created: time.Now(), @@ -262,7 +262,7 @@ func (oldkey *Key) AddKey(be backend.Server, password string) (backend.ID, error } // store in repository and return - id, err := be.Create(backend.Key, buf) + id, err := s.Create(backend.Key, buf) if err != nil { return nil, err } @@ -426,25 +426,6 @@ func (k *Key) DecryptUser(ciphertext []byte) ([]byte, error) { return k.decrypt(k.user, ciphertext) } -// Each calls backend.Each() with the given parameters, Decrypt() on the -// ciphertext and, on successful decryption, f with the plaintext. -func (k *Key) Each(be backend.Server, t backend.Type, f func(backend.ID, []byte, error)) error { - return backend.Each(be, t, func(id backend.ID, data []byte, e error) { - if e != nil { - f(id, nil, e) - return - } - - buf, err := k.Decrypt(data) - if err != nil { - f(id, nil, err) - return - } - - f(id, buf, nil) - }) -} - func (k *Key) String() string { if k == nil { return "" diff --git a/key_test.go b/key_test.go index 49e47ed5c..6fdcca836 100644 --- a/key_test.go +++ b/key_test.go @@ -15,42 +15,43 @@ import ( var testPassword = "foobar" var testCleanup = flag.Bool("test.cleanup", true, "clean up after running tests (remove local backend directory with all content)") -func setupBackend(t testing.TB) *backend.Local { +func setupBackend(t testing.TB) restic.Server { tempdir, err := ioutil.TempDir("", "restic-test-") ok(t, err) b, err := backend.CreateLocal(tempdir) ok(t, err) - return b + return restic.NewServer(b) } -func teardownBackend(t testing.TB, b *backend.Local) { +func teardownBackend(t testing.TB, s restic.Server) { if !*testCleanup { - t.Logf("leaving local backend at %s\n", b.Location()) + l := s.Backend().(*backend.Local) + t.Logf("leaving local backend at %s\n", l.Location()) return } - ok(t, os.RemoveAll(b.Location())) + ok(t, s.Delete()) } -func setupKey(t testing.TB, be backend.Server, password string) *restic.Key { - k, err := restic.CreateKey(be, password) +func setupKey(t testing.TB, s restic.Server, password string) *restic.Key { + k, err := restic.CreateKey(s, password) ok(t, err) return k } func TestRepo(t *testing.T) { - be := setupBackend(t) - defer teardownBackend(t, be) - _ = setupKey(t, be, testPassword) + s := setupBackend(t) + defer teardownBackend(t, s) + _ = setupKey(t, s, testPassword) } func TestEncryptDecrypt(t *testing.T) { - be := setupBackend(t) - defer teardownBackend(t, be) - k := setupKey(t, be, testPassword) + s := setupBackend(t) + defer teardownBackend(t, s) + k := setupKey(t, s, testPassword) for _, size := range []int{5, 23, 1 << 20, 7<<20 + 123} { data := make([]byte, size) @@ -74,9 +75,9 @@ func TestEncryptDecrypt(t *testing.T) { } func TestLargeEncrypt(t *testing.T) { - be := setupBackend(t) - defer teardownBackend(t, be) - k := setupKey(t, be, testPassword) + s := setupBackend(t) + defer teardownBackend(t, s) + k := setupKey(t, s, testPassword) for _, size := range []int{chunker.MaxSize, chunker.MaxSize + 1} { data := make([]byte, size) @@ -120,9 +121,9 @@ func BenchmarkDecrypt(b *testing.B) { size := 8 << 20 // 8MiB data := make([]byte, size) - be := setupBackend(b) - defer teardownBackend(b, be) - k := setupKey(b, be, testPassword) + s := setupBackend(b) + defer teardownBackend(b, s) + k := setupKey(b, s, testPassword) ciphertext := restic.GetChunkBuf("BenchmarkDecrypt") n, err := k.Encrypt(ciphertext, data) diff --git a/restorer.go b/restorer.go index 83dd18da3..7650e60b8 100644 --- a/restorer.go +++ b/restorer.go @@ -11,7 +11,7 @@ import ( ) type Restorer struct { - be backend.Server + s Server key *Key ch *ContentHandler sn *Snapshot @@ -21,14 +21,14 @@ type Restorer struct { } // NewRestorer creates a restorer preloaded with the content from the snapshot snid. -func NewRestorer(be backend.Server, key *Key, snid backend.ID) (*Restorer, error) { +func NewRestorer(s Server, key *Key, snid backend.ID) (*Restorer, error) { r := &Restorer{ - be: be, + s: s, key: key, } var err error - r.ch, err = NewContentHandler(be, key) + r.ch, err = NewContentHandler(s, key) if err != nil { return nil, arrar.Annotate(err, "create contenthandler for restorer") } diff --git a/server.go b/server.go new file mode 100644 index 000000000..dec529b31 --- /dev/null +++ b/server.go @@ -0,0 +1,113 @@ +package restic + +import ( + "errors" + + "github.com/restic/restic/backend" +) + +type Server struct { + be backend.Backend + key *Key +} + +func NewServer(be backend.Backend) Server { + return Server{be: be} +} + +func NewServerWithKey(be backend.Backend, key *Key) Server { + return Server{be: be, key: key} +} + +// Each lists all entries of type t in the backend and calls function f() with +// the id and data. +func (s Server) Each(t backend.Type, f func(id backend.ID, data []byte, err error)) error { + return backend.Each(s.be, t, f) +} + +// Each lists all entries of type t in the backend and calls function f() with +// the id. +func (s Server) EachID(t backend.Type, f func(backend.ID)) error { + return backend.EachID(s.be, t, f) +} + +// Find loads the list of all blobs of type t and searches for IDs which start +// with prefix. If none is found, nil and ErrNoIDPrefixFound is returned. If +// more than one is found, nil and ErrMultipleIDMatches is returned. +func (s Server) Find(t backend.Type, prefix string) (backend.ID, error) { + return backend.Find(s.be, t, prefix) +} + +// FindSnapshot takes a string and tries to find a snapshot whose ID matches +// the string as closely as possible. +func (s Server) FindSnapshot(id string) (backend.ID, error) { + return backend.FindSnapshot(s.be, id) +} + +// PrefixLength returns the number of bytes required so that all prefixes of +// all IDs of type t are unique. +func (s Server) PrefixLength(t backend.Type) (int, error) { + return backend.PrefixLength(s.be, t) +} + +// Returns the backend used for this server. +func (s Server) Backend() backend.Backend { + return s.be +} + +// Each calls Each() with the given parameters, Decrypt() on the ciphertext +// and, on successful decryption, f with the plaintext. +func (s Server) EachDecrypted(t backend.Type, f func(backend.ID, []byte, error)) error { + if s.key == nil { + return errors.New("key for server not set") + } + + return s.Each(t, func(id backend.ID, data []byte, e error) { + if e != nil { + f(id, nil, e) + return + } + + buf, err := s.key.Decrypt(data) + if err != nil { + f(id, nil, err) + return + } + + f(id, buf, nil) + }) +} + +// Proxy methods to backend + +func (s Server) List(t backend.Type) (backend.IDs, error) { + return s.be.List(t) +} + +func (s Server) Get(t backend.Type, id backend.ID) ([]byte, error) { + return s.be.Get(t, id) +} + +func (s Server) Create(t backend.Type, data []byte) (backend.ID, error) { + return s.be.Create(t, data) +} + +func (s Server) Test(t backend.Type, id backend.ID) (bool, error) { + return s.be.Test(t, id) +} + +func (s Server) Remove(t backend.Type, id backend.ID) error { + return s.be.Remove(t, id) +} + +func (s Server) Close() error { + return s.be.Close() +} + +func (s Server) Delete() error { + if b, ok := s.be.(backend.Deleter); ok { + return b.Delete() + } + + return errors.New("Delete() called for backend that does not implement this method") +} diff --git a/snapshot_test.go b/snapshot_test.go index fd441d0a7..39213d20f 100644 --- a/snapshot_test.go +++ b/snapshot_test.go @@ -8,7 +8,7 @@ import ( "github.com/restic/restic/backend" ) -func testSnapshot(t *testing.T, be backend.Server) { +func testSnapshot(t *testing.T, s restic.Server) { var err error sn, err := restic.NewSnapshot("/home/foobar") ok(t, err) From ef41a77aff7febf65e95b8490fbbe70e4d568c40 Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Sun, 21 Dec 2014 18:10:19 +0100 Subject: [PATCH 3/4] Remove explicit key handling --- archiver.go | 10 +++--- cmd/restic/cmd_backup.go | 8 ++--- cmd/restic/cmd_cat.go | 16 +++++----- cmd/restic/cmd_find.go | 14 ++++---- cmd/restic/cmd_fsck.go | 14 ++++---- cmd/restic/cmd_key.go | 36 ++++++++++----------- cmd/restic/cmd_list.go | 8 ++--- cmd/restic/cmd_ls.go | 8 ++--- cmd/restic/cmd_restore.go | 6 ++-- cmd/restic/cmd_snapshots.go | 8 ++--- cmd/restic/main.go | 64 +++---------------------------------- contenthandler.go | 18 +++++------ restorer.go | 16 ++++------ server.go | 31 ++++++++++++++++++ 14 files changed, 113 insertions(+), 144 deletions(-) diff --git a/archiver.go b/archiver.go index aba53cbcb..594b56673 100644 --- a/archiver.go +++ b/archiver.go @@ -22,9 +22,8 @@ const ( ) type Archiver struct { - s Server - key *Key - ch *ContentHandler + s Server + ch *ContentHandler bl *BlobList // blobs used for the current snapshot parentBl *BlobList // blobs from the parent snapshot @@ -58,11 +57,10 @@ func (s *Stats) Add(other Stats) { s.Other += other.Other } -func NewArchiver(s Server, key *Key) (*Archiver, error) { +func NewArchiver(s Server) (*Archiver, error) { var err error arch := &Archiver{ s: s, - key: key, fileToken: make(chan struct{}, maxConcurrentFiles), blobToken: make(chan struct{}, maxConcurrentBlobs), } @@ -82,7 +80,7 @@ func NewArchiver(s Server, key *Key) (*Archiver, error) { arch.Filter = func(string, os.FileInfo) bool { return true } arch.bl = NewBlobList() - arch.ch, err = NewContentHandler(s, key) + arch.ch, err = NewContentHandler(s) if err != nil { return nil, err } diff --git a/cmd/restic/cmd_backup.go b/cmd/restic/cmd_backup.go index 24f2bb7c8..03365635a 100644 --- a/cmd/restic/cmd_backup.go +++ b/cmd/restic/cmd_backup.go @@ -72,7 +72,7 @@ func (cmd CmdBackup) Execute(args []string) error { return fmt.Errorf("wrong number of parameters, Usage: %s", cmd.Usage()) } - be, key, err := OpenRepo() + s, err := OpenRepo() if err != nil { return err } @@ -81,7 +81,7 @@ func (cmd CmdBackup) Execute(args []string) error { target := args[0] if len(args) > 1 { - parentSnapshotID, err = backend.FindSnapshot(be, args[1]) + parentSnapshotID, err = s.FindSnapshot(args[1]) if err != nil { return fmt.Errorf("invalid id %q: %v", args[1], err) } @@ -89,7 +89,7 @@ func (cmd CmdBackup) Execute(args []string) error { fmt.Printf("found parent snapshot %v\n", parentSnapshotID) } - arch, err := restic.NewArchiver(be, key) + arch, err := restic.NewArchiver(s) if err != nil { fmt.Fprintf(os.Stderr, "err: %v\n", err) } @@ -183,7 +183,7 @@ func (cmd CmdBackup) Execute(args []string) error { close(arch.ScannerStats) } - plen, err := backend.PrefixLength(be, backend.Snapshot) + plen, err := s.PrefixLength(backend.Snapshot) if err != nil { return err } diff --git a/cmd/restic/cmd_cat.go b/cmd/restic/cmd_cat.go index 6af0a476a..2ee657bc3 100644 --- a/cmd/restic/cmd_cat.go +++ b/cmd/restic/cmd_cat.go @@ -31,7 +31,7 @@ func (cmd CmdCat) Execute(args []string) error { return fmt.Errorf("type or ID not specified, Usage: %s", cmd.Usage()) } - be, key, err := OpenRepo() + s, err := OpenRepo() if err != nil { return err } @@ -47,13 +47,13 @@ func (cmd CmdCat) Execute(args []string) error { } // find snapshot id with prefix - id, err = backend.Find(be, backend.Snapshot, args[1]) + id, err = s.FindSnapshot(args[1]) if err != nil { return err } } - ch, err := restic.NewContentHandler(be, key) + ch, err := restic.NewContentHandler(s) if err != nil { return err } @@ -73,13 +73,13 @@ func (cmd CmdCat) Execute(args []string) error { } // try storage id - buf, err := be.Get(backend.Data, id) + buf, err := s.Get(backend.Data, id) if err != nil { return err } // decrypt - buf, err = key.Decrypt(buf) + buf, err = s.Decrypt(buf) if err != nil { return err } @@ -98,13 +98,13 @@ func (cmd CmdCat) Execute(args []string) error { err := ch.LoadJSON(backend.Tree, id, &tree) if err != nil { // try storage id - buf, err := be.Get(backend.Tree, id) + buf, err := s.Get(backend.Tree, id) if err != nil { return err } // decrypt - buf, err = key.Decrypt(buf) + buf, err = s.Decrypt(buf) if err != nil { return err } @@ -156,7 +156,7 @@ func (cmd CmdCat) Execute(args []string) error { return nil case "key": - data, err := be.Get(backend.Key, id) + data, err := s.Get(backend.Key, id) if err != nil { return err } diff --git a/cmd/restic/cmd_find.go b/cmd/restic/cmd_find.go index 585e60339..55cec11be 100644 --- a/cmd/restic/cmd_find.go +++ b/cmd/restic/cmd_find.go @@ -110,10 +110,10 @@ func (c CmdFind) findInTree(ch *restic.ContentHandler, id backend.ID, path strin return results, nil } -func (c CmdFind) findInSnapshot(be restic.Server, key *restic.Key, id backend.ID) error { +func (c CmdFind) findInSnapshot(s restic.Server, id backend.ID) error { debug("searching in snapshot %s\n for entries within [%s %s]", id, c.oldest, c.newest) - ch, err := restic.NewContentHandler(be, key) + ch, err := restic.NewContentHandler(s) if err != nil { return err } @@ -166,7 +166,7 @@ func (c CmdFind) Execute(args []string) error { } } - be, key, err := OpenRepo() + s, err := OpenRepo() if err != nil { return err } @@ -174,21 +174,21 @@ func (c CmdFind) Execute(args []string) error { c.pattern = args[0] if c.Snapshot != "" { - snapshotID, err := backend.FindSnapshot(be, c.Snapshot) + snapshotID, err := backend.FindSnapshot(s, c.Snapshot) if err != nil { return fmt.Errorf("invalid id %q: %v", args[1], err) } - return c.findInSnapshot(be, key, snapshotID) + return c.findInSnapshot(s, snapshotID) } - list, err := be.List(backend.Snapshot) + list, err := s.List(backend.Snapshot) if err != nil { return err } for _, snapshotID := range list { - err := c.findInSnapshot(be, key, snapshotID) + err := c.findInSnapshot(s, snapshotID) if err != nil { return err diff --git a/cmd/restic/cmd_fsck.go b/cmd/restic/cmd_fsck.go index fcb1c6329..ae80f0f2a 100644 --- a/cmd/restic/cmd_fsck.go +++ b/cmd/restic/cmd_fsck.go @@ -75,10 +75,10 @@ func fsckTree(ch *restic.ContentHandler, id backend.ID) error { return nil } -func fsck_snapshot(be restic.Server, key *restic.Key, id backend.ID) error { +func fsck_snapshot(s restic.Server, id backend.ID) error { debug("checking snapshot %v\n", id) - ch, err := restic.NewContentHandler(be, key) + ch, err := restic.NewContentHandler(s) if err != nil { return err } @@ -108,27 +108,27 @@ func (cmd CmdFsck) Execute(args []string) error { return fmt.Errorf("type or ID not specified, Usage: %s", cmd.Usage()) } - be, key, err := OpenRepo() + s, err := OpenRepo() if err != nil { return err } if len(args) == 1 && args[0] != "all" { - snapshotID, err := backend.FindSnapshot(be, args[0]) + snapshotID, err := s.FindSnapshot(args[0]) if err != nil { return fmt.Errorf("invalid id %q: %v", args[0], err) } - return fsck_snapshot(be, key, snapshotID) + return fsck_snapshot(s, snapshotID) } - list, err := be.List(backend.Snapshot) + list, err := s.List(backend.Snapshot) if err != nil { return err } for _, snapshotID := range list { - err := fsck_snapshot(be, key, snapshotID) + err := fsck_snapshot(s, snapshotID) if err != nil { return err diff --git a/cmd/restic/cmd_key.go b/cmd/restic/cmd_key.go index 9332ebb8c..c502fc3db 100644 --- a/cmd/restic/cmd_key.go +++ b/cmd/restic/cmd_key.go @@ -22,17 +22,17 @@ func init() { } } -func list_keys(be restic.Server, key *restic.Key) error { +func list_keys(s restic.Server) error { tab := NewTable() tab.Header = fmt.Sprintf(" %-10s %-10s %-10s %s", "ID", "User", "Host", "Created") tab.RowFormat = "%s%-10s %-10s %-10s %s" - plen, err := backend.PrefixLength(be, backend.Key) + plen, err := s.PrefixLength(backend.Key) if err != nil { return err } - backend.Each(be, backend.Key, func(id backend.ID, data []byte, err error) { + s.Each(backend.Key, func(id backend.ID, data []byte, err error) { k := restic.Key{} err = json.Unmarshal(data, &k) if err != nil { @@ -40,7 +40,7 @@ func list_keys(be restic.Server, key *restic.Key) error { } var current string - if id.Equal(key.ID()) { + if id.Equal(s.Key().ID()) { current = "*" } else { current = " " @@ -54,7 +54,7 @@ func list_keys(be restic.Server, key *restic.Key) error { return nil } -func add_key(be restic.Server, key *restic.Key) error { +func add_key(s restic.Server) error { pw := readPassword("RESTIC_NEWPASSWORD", "enter password for new key: ") pw2 := readPassword("RESTIC_NEWPASSWORD", "enter password again: ") @@ -62,7 +62,7 @@ func add_key(be restic.Server, key *restic.Key) error { return errors.New("passwords do not match") } - id, err := key.AddKey(be, pw) + id, err := s.Key().AddKey(s, pw) if err != nil { return fmt.Errorf("creating new key failed: %v\n", err) } @@ -72,12 +72,12 @@ func add_key(be restic.Server, key *restic.Key) error { return nil } -func delete_key(be restic.Server, key *restic.Key, id backend.ID) error { - if id.Equal(key.ID()) { +func delete_key(s restic.Server, id backend.ID) error { + if id.Equal(s.Key().ID()) { return errors.New("refusing to remove key currently used to access repository") } - err := be.Remove(backend.Key, id) + err := s.Remove(backend.Key, id) if err != nil { return err } @@ -86,7 +86,7 @@ func delete_key(be restic.Server, key *restic.Key, id backend.ID) error { return nil } -func change_password(be restic.Server, key *restic.Key) error { +func change_password(s restic.Server) error { pw := readPassword("RESTIC_NEWPASSWORD", "enter password for new key: ") pw2 := readPassword("RESTIC_NEWPASSWORD", "enter password again: ") @@ -95,13 +95,13 @@ func change_password(be restic.Server, key *restic.Key) error { } // add new key - id, err := key.AddKey(be, pw) + id, err := s.Key().AddKey(s, pw) if err != nil { return fmt.Errorf("creating new key failed: %v\n", err) } // remove old key - err = be.Remove(backend.Key, key.ID()) + err = s.Remove(backend.Key, s.Key().ID()) if err != nil { return err } @@ -120,25 +120,25 @@ func (cmd CmdKey) Execute(args []string) error { return fmt.Errorf("wrong number of arguments, Usage: %s", cmd.Usage()) } - be, key, err := OpenRepo() + s, err := OpenRepo() if err != nil { return err } switch args[0] { case "list": - return list_keys(be, key) + return list_keys(s) case "add": - return add_key(be, key) + return add_key(s) case "rm": - id, err := backend.Find(be, backend.Key, args[1]) + id, err := backend.Find(s, backend.Key, args[1]) if err != nil { return err } - return delete_key(be, key, id) + return delete_key(s, id) case "change": - return change_password(be, key) + return change_password(s) } return nil diff --git a/cmd/restic/cmd_list.go b/cmd/restic/cmd_list.go index b85b9f010..5d43d8eed 100644 --- a/cmd/restic/cmd_list.go +++ b/cmd/restic/cmd_list.go @@ -28,22 +28,22 @@ func (cmd CmdList) Execute(args []string) error { return fmt.Errorf("type not specified, Usage: %s", cmd.Usage()) } - be, _, err := OpenRepo() + s, err := OpenRepo() if err != nil { return err } var ( t backend.Type - each func(backend.Type, func(backend.ID, []byte, error)) error = be.Each + each func(backend.Type, func(backend.ID, []byte, error)) error = s.Each ) switch args[0] { case "data": t = backend.Data - each = be.EachDecrypted + each = s.EachDecrypted case "trees": t = backend.Tree - each = be.EachDecrypted + each = s.EachDecrypted case "snapshots": t = backend.Snapshot case "maps": diff --git a/cmd/restic/cmd_ls.go b/cmd/restic/cmd_ls.go index b64454a43..94d2b04bb 100644 --- a/cmd/restic/cmd_ls.go +++ b/cmd/restic/cmd_ls.go @@ -63,22 +63,22 @@ func (cmd CmdLs) Usage() string { return "ls snapshot-ID [DIR]" } -func (cmd CmdLs) Execute(be restic.Server, key *restic.Key, args []string) error { +func (cmd CmdLs) Execute(s restic.Server, key *restic.Key, args []string) error { if len(args) < 1 || len(args) > 2 { return fmt.Errorf("wrong number of arguments, Usage: %s", cmd.Usage()) } - be, key, err := OpenRepo() + s, err := OpenRepo() if err != nil { return err } - id, err := backend.FindSnapshot(be, args[0]) + id, err := backend.FindSnapshot(s, args[0]) if err != nil { return err } - ch, err := restic.NewContentHandler(be, key) + ch, err := restic.NewContentHandler(s) if err != nil { return err } diff --git a/cmd/restic/cmd_restore.go b/cmd/restic/cmd_restore.go index 2dbe9ea74..b165f2805 100644 --- a/cmd/restic/cmd_restore.go +++ b/cmd/restic/cmd_restore.go @@ -29,12 +29,12 @@ func (cmd CmdRestore) Execute(args []string) error { return fmt.Errorf("wrong number of arguments, Usage: %s", cmd.Usage()) } - be, key, err := OpenRepo() + s, err := OpenRepo() if err != nil { return err } - id, err := backend.FindSnapshot(be, args[0]) + id, err := backend.FindSnapshot(s, args[0]) if err != nil { errx(1, "invalid id %q: %v", args[0], err) } @@ -42,7 +42,7 @@ func (cmd CmdRestore) Execute(args []string) error { target := args[1] // create restorer - res, err := restic.NewRestorer(be, key, id) + res, err := restic.NewRestorer(s, id) if err != nil { fmt.Fprintf(os.Stderr, "creating restorer failed: %v\n", err) os.Exit(2) diff --git a/cmd/restic/cmd_snapshots.go b/cmd/restic/cmd_snapshots.go index c431607e3..036ca49b9 100644 --- a/cmd/restic/cmd_snapshots.go +++ b/cmd/restic/cmd_snapshots.go @@ -92,12 +92,12 @@ func (cmd CmdSnapshots) Execute(args []string) error { return fmt.Errorf("wrong number of arguments, usage: %s", cmd.Usage()) } - be, key, err := OpenRepo() + s, err := OpenRepo() if err != nil { return err } - ch, err := restic.NewContentHandler(be, key) + ch, err := restic.NewContentHandler(s) if err != nil { return err } @@ -107,7 +107,7 @@ func (cmd CmdSnapshots) Execute(args []string) error { tab.RowFormat = "%-8s %-19s %-10s %s" list := []*restic.Snapshot{} - backend.EachID(be, backend.Snapshot, func(id backend.ID) { + s.EachID(backend.Snapshot, func(id backend.ID) { sn, err := restic.LoadSnapshot(ch, id) if err != nil { fmt.Fprintf(os.Stderr, "error loading snapshot %s: %v\n", id, err) @@ -127,7 +127,7 @@ func (cmd CmdSnapshots) Execute(args []string) error { } }) - plen, err := backend.PrefixLength(be, backend.Snapshot) + plen, err := s.PrefixLength(backend.Snapshot) if err != nil { return err } diff --git a/cmd/restic/main.go b/cmd/restic/main.go index ee2f835bf..5fec20030 100644 --- a/cmd/restic/main.go +++ b/cmd/restic/main.go @@ -129,20 +129,20 @@ func create(u string) (backend.Backend, error) { return backend.CreateSFTP(url.Path[1:], "ssh", args...) } -func OpenRepo() (restic.Server, *restic.Key, error) { +func OpenRepo() (restic.Server, error) { be, err := open(opts.Repo) if err != nil { - return restic.Server{}, nil, err + return restic.Server{}, err } s := restic.NewServer(be) - key, err := restic.SearchKey(s, readPassword("RESTIC_PASSWORD", "Enter Password for Repository: ")) + err = s.SearchKey(readPassword("RESTIC_PASSWORD", "Enter Password for Repository: ")) if err != nil { - return restic.Server{}, nil, fmt.Errorf("unable to open repo: %v", err) + return restic.Server{}, fmt.Errorf("unable to open repo: %v", err) } - return s, key, nil + return s, nil } func init() { @@ -171,59 +171,5 @@ func main() { os.Exit(1) } - // fmt.Printf("parser: %#v\n", parser) - // fmt.Printf("%#v\n", parser.Active.Name) - - // if opts.Repo == "" { - // fmt.Fprintf(os.Stderr, "no repository specified, use -r or RESTIC_REPOSITORY variable\n") - // os.Exit(1) - // } - - // if len(args) == 0 { - // cmds := []string{"init"} - // for k := range commands { - // cmds = append(cmds, k) - // } - // sort.Strings(cmds) - // fmt.Printf("nothing to do, available commands: [%v]\n", strings.Join(cmds, "|")) - // os.Exit(0) - // } - - // cmd := args[0] - - // switch cmd { - // case "init": - // err = commandInit(opts.Repo) - // if err != nil { - // errx(1, "error executing command %q: %v", cmd, err) - // } - // return - - // case "version": - // fmt.Printf("%v\n", version) - // return - // } - - // f, ok := commands[cmd] - // if !ok { - // errx(1, "unknown command: %q\n", cmd) - // } - - // // read_password("enter password: ") - // repo, err := open(opts.Repo) - // if err != nil { - // errx(1, "unable to open repo: %v", err) - // } - - // key, err := restic.SearchKey(repo, readPassword("RESTIC_PASSWORD", "Enter Password for Repository: ")) - // if err != nil { - // errx(2, "unable to open repo: %v", err) - // } - - // err = f(repo, key, args[1:]) - // if err != nil { - // errx(1, "error executing command %q: %v", cmd, err) - // } - // restic.PoolAlloc() } diff --git a/contenthandler.go b/contenthandler.go index 3beed5f09..16fd7a2ab 100644 --- a/contenthandler.go +++ b/contenthandler.go @@ -11,18 +11,16 @@ import ( var ErrWrongData = errors.New("wrong data decrypt, checksum does not match") type ContentHandler struct { - s Server - key *Key + s Server bl *BlobList } // NewContentHandler creates a new content handler. -func NewContentHandler(s Server, key *Key) (*ContentHandler, error) { +func NewContentHandler(s Server) (*ContentHandler, error) { ch := &ContentHandler{ - s: s, - key: key, - bl: NewBlobList(), + s: s, + bl: NewBlobList(), } return ch, nil @@ -95,7 +93,7 @@ func (ch *ContentHandler) Save(t backend.Type, data []byte) (Blob, error) { } // encrypt blob - n, err := ch.key.Encrypt(ciphertext, data) + n, err := ch.s.Encrypt(ciphertext, data) if err != nil { return Blob{}, err } @@ -139,7 +137,7 @@ func (ch *ContentHandler) Load(t backend.Type, id backend.ID) ([]byte, error) { } // decrypt - buf, err = ch.key.Decrypt(buf) + buf, err = ch.s.Decrypt(buf) if err != nil { return nil, err } @@ -165,7 +163,7 @@ func (ch *ContentHandler) Load(t backend.Type, id backend.ID) ([]byte, error) { } // decrypt - buf, err = ch.key.Decrypt(buf) + buf, err = ch.s.Decrypt(buf) if err != nil { return nil, err } @@ -207,7 +205,7 @@ func (ch *ContentHandler) LoadJSONRaw(t backend.Type, id backend.ID, item interf } // decrypt - buf, err = ch.key.Decrypt(buf) + buf, err = ch.s.Decrypt(buf) if err != nil { return err } diff --git a/restorer.go b/restorer.go index 7650e60b8..f26b30354 100644 --- a/restorer.go +++ b/restorer.go @@ -11,24 +11,20 @@ import ( ) type Restorer struct { - s Server - key *Key - ch *ContentHandler - sn *Snapshot + s Server + ch *ContentHandler + sn *Snapshot Error func(dir string, node *Node, err error) error Filter func(item string, node *Node) bool } // NewRestorer creates a restorer preloaded with the content from the snapshot snid. -func NewRestorer(s Server, key *Key, snid backend.ID) (*Restorer, error) { - r := &Restorer{ - s: s, - key: key, - } +func NewRestorer(s Server, snid backend.ID) (*Restorer, error) { + r := &Restorer{s: s} var err error - r.ch, err = NewContentHandler(s, key) + r.ch, err = NewContentHandler(s) if err != nil { return nil, arrar.Annotate(err, "create contenthandler for restorer") } diff --git a/server.go b/server.go index dec529b31..563971ffb 100644 --- a/server.go +++ b/server.go @@ -55,6 +55,37 @@ func (s Server) Backend() backend.Backend { return s.be } +func (s *Server) SearchKey(password string) error { + key, err := SearchKey(*s, password) + if err != nil { + return err + } + + s.key = key + + return nil +} + +func (s Server) Decrypt(ciphertext []byte) ([]byte, error) { + if s.key == nil { + return nil, errors.New("key for server not set") + } + + return s.key.Decrypt(ciphertext) +} + +func (s Server) Encrypt(ciphertext, plaintext []byte) (int, error) { + if s.key == nil { + return 0, errors.New("key for server not set") + } + + return s.key.Encrypt(ciphertext, plaintext) +} + +func (s Server) Key() *Key { + return s.key +} + // Each calls Each() with the given parameters, Decrypt() on the ciphertext // and, on successful decryption, f with the plaintext. func (s Server) EachDecrypted(t backend.Type, f func(backend.ID, []byte, error)) error { From 5431b025a3ce7721e3f037a5d6cbc67a31248ed3 Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Sun, 21 Dec 2014 18:16:22 +0100 Subject: [PATCH 4/4] Reduce code duplication in key handling --- cmd/restic/cmd_key.go | 4 +-- key.go | 81 +++++++------------------------------------ 2 files changed, 15 insertions(+), 70 deletions(-) diff --git a/cmd/restic/cmd_key.go b/cmd/restic/cmd_key.go index c502fc3db..bd89ac46d 100644 --- a/cmd/restic/cmd_key.go +++ b/cmd/restic/cmd_key.go @@ -62,7 +62,7 @@ func add_key(s restic.Server) error { return errors.New("passwords do not match") } - id, err := s.Key().AddKey(s, pw) + id, err := restic.AddKey(s, pw, s.Key()) if err != nil { return fmt.Errorf("creating new key failed: %v\n", err) } @@ -95,7 +95,7 @@ func change_password(s restic.Server) error { } // add new key - id, err := s.Key().AddKey(s, pw) + id, err := restic.AddKey(s, pw, s.Key()) if err != nil { return fmt.Errorf("creating new key failed: %v\n", err) } diff --git a/key.go b/key.go index 4bfad1380..369536228 100644 --- a/key.go +++ b/key.go @@ -76,70 +76,7 @@ type keys struct { // CreateKey initializes a master key in the given backend and encrypts it with // the password. func CreateKey(s Server, password string) (*Key, error) { - // fill meta data about key - k := &Key{ - Created: time.Now(), - KDF: "scrypt", - N: scryptN, - R: scryptR, - P: scryptP, - } - - hn, err := os.Hostname() - if err == nil { - k.Hostname = hn - } - - usr, err := user.Current() - if err == nil { - k.Username = usr.Username - } - - // generate random salt - k.Salt = make([]byte, scryptSaltsize) - n, err := rand.Read(k.Salt) - if n != scryptSaltsize || err != nil { - panic("unable to read enough random bytes for salt") - } - - // call scrypt() to derive user key - k.user, err = k.scrypt(password) - if err != nil { - return nil, err - } - - // generate new random master keys - k.master, err = k.newKeys() - if err != nil { - return nil, err - } - - // encrypt master keys (as json) with user key - buf, err := json.Marshal(k.master) - if err != nil { - return nil, err - } - - k.Data = GetChunkBuf("key") - n, err = k.EncryptUser(k.Data, buf) - k.Data = k.Data[:n] - - // dump as json - buf, err = json.Marshal(k) - if err != nil { - return nil, err - } - - // store in repository and return - id, err := s.Create(backend.Key, buf) - if err != nil { - return nil, err - } - k.id = id - - FreeChunkBuf("key", k.Data) - - return k, nil + return AddKey(s, password, nil) } // OpenKey tries do decrypt the key specified by id with the given password. @@ -209,7 +146,7 @@ func SearchKey(s Server, password string) (*Key, error) { } // AddKey adds a new key to an already existing repository. -func (oldkey *Key) AddKey(s Server, password string) (backend.ID, error) { +func AddKey(s Server, password string, template *Key) (*Key, error) { // fill meta data about key newkey := &Key{ Created: time.Now(), @@ -242,8 +179,16 @@ func (oldkey *Key) AddKey(s Server, password string) (backend.ID, error) { return nil, err } - // copy master keys from oldkey - newkey.master = oldkey.master + if template == nil { + // generate new random master keys + newkey.master, err = newkey.newKeys() + if err != nil { + return nil, err + } + } else { + // copy master keys from old key + newkey.master = template.master + } // encrypt master keys (as json) with user key buf, err := json.Marshal(newkey.master) @@ -270,7 +215,7 @@ func (oldkey *Key) AddKey(s Server, password string) (backend.ID, error) { FreeChunkBuf("key", newkey.Data) - return id, nil + return newkey, nil } func (k *Key) scrypt(password string) (*keys, error) {