forked from TrueCloudLab/restic
backends: Add Save()
This commit is contained in:
parent
ed172c06e0
commit
54f8860612
10 changed files with 233 additions and 11 deletions
|
@ -33,7 +33,7 @@ func (h Handle) Valid() error {
|
||||||
case Index:
|
case Index:
|
||||||
case Config:
|
case Config:
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("invalid config %q", h.Type)
|
return fmt.Errorf("invalid Type %q", h.Type)
|
||||||
}
|
}
|
||||||
|
|
||||||
if h.Type == Config {
|
if h.Type == Config {
|
||||||
|
|
|
@ -58,6 +58,20 @@ func TestLocalBackendWrite(t *testing.T) {
|
||||||
test.TestWrite(t)
|
test.TestWrite(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLocalBackendSave(t *testing.T) {
|
||||||
|
if SkipMessage != "" {
|
||||||
|
t.Skip(SkipMessage)
|
||||||
|
}
|
||||||
|
test.TestSave(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLocalBackendSaveFilenames(t *testing.T) {
|
||||||
|
if SkipMessage != "" {
|
||||||
|
t.Skip(SkipMessage)
|
||||||
|
}
|
||||||
|
test.TestSaveFilenames(t)
|
||||||
|
}
|
||||||
|
|
||||||
func TestLocalBackendBackend(t *testing.T) {
|
func TestLocalBackendBackend(t *testing.T) {
|
||||||
if SkipMessage != "" {
|
if SkipMessage != "" {
|
||||||
t.Skip(SkipMessage)
|
t.Skip(SkipMessage)
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/restic/restic/backend"
|
"github.com/restic/restic/backend"
|
||||||
|
"github.com/restic/restic/debug"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrWrongData = errors.New("wrong data returned by backend, checksum does not match")
|
var ErrWrongData = errors.New("wrong data returned by backend, checksum does not match")
|
||||||
|
@ -231,12 +232,14 @@ func (b *Local) Save(h backend.Handle, p []byte) (err error) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
f, err := os.Create(filename(b.p, h.Type, h.Name))
|
tmpfile, err := ioutil.TempFile(filepath.Join(b.p, backend.Paths.Temp), "temp-")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
n, err := f.Write(p)
|
debug.Log("local.Save", "save %v (%d bytes) to %v", h, len(p), tmpfile.Name())
|
||||||
|
|
||||||
|
n, err := tmpfile.Write(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -245,11 +248,58 @@ func (b *Local) Save(h backend.Handle, p []byte) (err error) {
|
||||||
return errors.New("not all bytes writen")
|
return errors.New("not all bytes writen")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = f.Sync(); err != nil {
|
if err = tmpfile.Sync(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return f.Close()
|
err = tmpfile.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
f := filename(b.p, h.Type, h.Name)
|
||||||
|
|
||||||
|
// create directories if necessary, ignore errors
|
||||||
|
if h.Type == backend.Data {
|
||||||
|
os.MkdirAll(filepath.Dir(f), backend.Modes.Dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
// test if new path already exists
|
||||||
|
if _, err := os.Stat(f); err == nil {
|
||||||
|
return fmt.Errorf("Rename(): file %v already exists", f)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.Rename(tmpfile.Name(), f)
|
||||||
|
debug.Log("local.Save", "save %v: rename %v -> %v: %v",
|
||||||
|
h, filepath.Base(tmpfile.Name()), filepath.Base(f), err)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// set mode to read-only
|
||||||
|
fi, err := os.Stat(f)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = setNewFileMode(f, fi)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// try to flush directory
|
||||||
|
d, err := os.Open(filepath.Dir(f))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = d.Sync()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return d.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stat returns information about a blob.
|
// Stat returns information about a blob.
|
||||||
|
|
|
@ -58,6 +58,20 @@ func TestMemBackendWrite(t *testing.T) {
|
||||||
test.TestWrite(t)
|
test.TestWrite(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMemBackendSave(t *testing.T) {
|
||||||
|
if SkipMessage != "" {
|
||||||
|
t.Skip(SkipMessage)
|
||||||
|
}
|
||||||
|
test.TestSave(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMemBackendSaveFilenames(t *testing.T) {
|
||||||
|
if SkipMessage != "" {
|
||||||
|
t.Skip(SkipMessage)
|
||||||
|
}
|
||||||
|
test.TestSaveFilenames(t)
|
||||||
|
}
|
||||||
|
|
||||||
func TestMemBackendBackend(t *testing.T) {
|
func TestMemBackendBackend(t *testing.T) {
|
||||||
if SkipMessage != "" {
|
if SkipMessage != "" {
|
||||||
t.Skip(SkipMessage)
|
t.Skip(SkipMessage)
|
||||||
|
|
|
@ -45,6 +45,10 @@ func New() *MemoryBackend {
|
||||||
return memLoad(be, h, p, off)
|
return memLoad(be, h, p, off)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
be.MockBackend.SaveFn = func(h backend.Handle, p []byte) error {
|
||||||
|
return memSave(be, h, p)
|
||||||
|
}
|
||||||
|
|
||||||
be.MockBackend.StatFn = func(h backend.Handle) (backend.BlobInfo, error) {
|
be.MockBackend.StatFn = func(h backend.Handle) (backend.BlobInfo, error) {
|
||||||
return memStat(be, h)
|
return memStat(be, h)
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,6 +58,20 @@ func TestS3BackendWrite(t *testing.T) {
|
||||||
test.TestWrite(t)
|
test.TestWrite(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestS3BackendSave(t *testing.T) {
|
||||||
|
if SkipMessage != "" {
|
||||||
|
t.Skip(SkipMessage)
|
||||||
|
}
|
||||||
|
test.TestSave(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestS3BackendSaveFilenames(t *testing.T) {
|
||||||
|
if SkipMessage != "" {
|
||||||
|
t.Skip(SkipMessage)
|
||||||
|
}
|
||||||
|
test.TestSaveFilenames(t)
|
||||||
|
}
|
||||||
|
|
||||||
func TestS3BackendBackend(t *testing.T) {
|
func TestS3BackendBackend(t *testing.T) {
|
||||||
if SkipMessage != "" {
|
if SkipMessage != "" {
|
||||||
t.Skip(SkipMessage)
|
t.Skip(SkipMessage)
|
||||||
|
|
|
@ -58,6 +58,20 @@ func TestSftpBackendWrite(t *testing.T) {
|
||||||
test.TestWrite(t)
|
test.TestWrite(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSftpBackendSave(t *testing.T) {
|
||||||
|
if SkipMessage != "" {
|
||||||
|
t.Skip(SkipMessage)
|
||||||
|
}
|
||||||
|
test.TestSave(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSftpBackendSaveFilenames(t *testing.T) {
|
||||||
|
if SkipMessage != "" {
|
||||||
|
t.Skip(SkipMessage)
|
||||||
|
}
|
||||||
|
test.TestSaveFilenames(t)
|
||||||
|
}
|
||||||
|
|
||||||
func TestSftpBackendBackend(t *testing.T) {
|
func TestSftpBackendBackend(t *testing.T) {
|
||||||
if SkipMessage != "" {
|
if SkipMessage != "" {
|
||||||
t.Skip(SkipMessage)
|
t.Skip(SkipMessage)
|
||||||
|
|
|
@ -15,6 +15,7 @@ import (
|
||||||
"github.com/juju/errors"
|
"github.com/juju/errors"
|
||||||
"github.com/pkg/sftp"
|
"github.com/pkg/sftp"
|
||||||
"github.com/restic/restic/backend"
|
"github.com/restic/restic/backend"
|
||||||
|
"github.com/restic/restic/debug"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -379,12 +380,10 @@ func (r *SFTP) Save(h backend.Handle, p []byte) (err error) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
f, err := r.c.Create(r.filename(h.Type, h.Name))
|
filename, tmpfile, err := r.tempFile()
|
||||||
if err != nil {
|
debug.Log("sftp.Save", "save %v (%d bytes) to %v", h, len(p), filename)
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
n, err := f.Write(p)
|
n, err := tmpfile.Write(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -393,7 +392,19 @@ func (r *SFTP) Save(h backend.Handle, p []byte) (err error) {
|
||||||
return errors.New("not all bytes writen")
|
return errors.New("not all bytes writen")
|
||||||
}
|
}
|
||||||
|
|
||||||
return f.Close()
|
err = tmpfile.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = r.renameFile(filename, h.Type, h.Name)
|
||||||
|
debug.Log("sftp.Save", "save %v: rename %v: %v",
|
||||||
|
h, filepath.Base(filename), err)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("sftp: renameFile: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stat returns information about a blob.
|
// Stat returns information about a blob.
|
||||||
|
|
|
@ -58,6 +58,20 @@ func TestTestBackendWrite(t *testing.T) {
|
||||||
test.TestWrite(t)
|
test.TestWrite(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTestBackendSave(t *testing.T) {
|
||||||
|
if SkipMessage != "" {
|
||||||
|
t.Skip(SkipMessage)
|
||||||
|
}
|
||||||
|
test.TestSave(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTestBackendSaveFilenames(t *testing.T) {
|
||||||
|
if SkipMessage != "" {
|
||||||
|
t.Skip(SkipMessage)
|
||||||
|
}
|
||||||
|
test.TestSaveFilenames(t)
|
||||||
|
}
|
||||||
|
|
||||||
func TestTestBackendBackend(t *testing.T) {
|
func TestTestBackendBackend(t *testing.T) {
|
||||||
if SkipMessage != "" {
|
if SkipMessage != "" {
|
||||||
t.Skip(SkipMessage)
|
t.Skip(SkipMessage)
|
||||||
|
|
|
@ -325,6 +325,93 @@ func TestWrite(t testing.TB) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestSave tests saving data in the backend.
|
||||||
|
func TestSave(t testing.TB) {
|
||||||
|
b := open(t)
|
||||||
|
defer close(t)
|
||||||
|
|
||||||
|
length := rand.Intn(1<<23) + 2000
|
||||||
|
data := make([]byte, length)
|
||||||
|
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
_, err := io.ReadFull(crand.Reader, data)
|
||||||
|
OK(t, err)
|
||||||
|
id := backend.Hash(data)
|
||||||
|
|
||||||
|
h := backend.Handle{
|
||||||
|
Type: backend.Data,
|
||||||
|
Name: fmt.Sprintf("%s-%d", id, i),
|
||||||
|
}
|
||||||
|
err = b.Save(h, data)
|
||||||
|
OK(t, err)
|
||||||
|
|
||||||
|
buf, err := backend.LoadAll(b, h, nil)
|
||||||
|
OK(t, err)
|
||||||
|
if len(buf) != len(data) {
|
||||||
|
t.Fatalf("number of bytes does not match, want %v, got %v", len(data), len(buf))
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(buf, data) {
|
||||||
|
t.Fatalf("data not equal")
|
||||||
|
}
|
||||||
|
|
||||||
|
fi, err := b.Stat(h)
|
||||||
|
OK(t, err)
|
||||||
|
|
||||||
|
if fi.Size != int64(len(data)) {
|
||||||
|
t.Fatalf("Stat() returned different size, want %q, got %d", len(data), fi.Size)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = b.Remove(h.Type, h.Name)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error removing item: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var filenameTests = []struct {
|
||||||
|
name string
|
||||||
|
data string
|
||||||
|
}{
|
||||||
|
{"1dfc6bc0f06cb255889e9ea7860a5753e8eb9665c9a96627971171b444e3113e", "x"},
|
||||||
|
{"foobar", "foobar"},
|
||||||
|
{
|
||||||
|
"1dfc6bc0f06cb255889e9ea7860a5753e8eb9665c9a96627971171b444e3113e4bf8f2d9144cc5420a80f04a4880ad6155fc58903a4fb6457c476c43541dcaa6-5",
|
||||||
|
"foobar content of data blob",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSaveFilenames tests saving data with various file names in the backend.
|
||||||
|
func TestSaveFilenames(t testing.TB) {
|
||||||
|
b := open(t)
|
||||||
|
defer close(t)
|
||||||
|
|
||||||
|
for i, test := range filenameTests {
|
||||||
|
h := backend.Handle{Name: test.name, Type: backend.Data}
|
||||||
|
err := b.Save(h, []byte(test.data))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("test %d failed: Save() returned %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := backend.LoadAll(b, h, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("test %d failed: Load() returned %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(buf, []byte(test.data)) {
|
||||||
|
t.Errorf("test %d: returned wrong bytes", i)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = b.Remove(h.Type, h.Name)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("test %d failed: Remove() returned %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var testStrings = []struct {
|
var testStrings = []struct {
|
||||||
id string
|
id string
|
||||||
data string
|
data string
|
||||||
|
|
Loading…
Reference in a new issue