Make sure we can delete files on Windows.
Files must be closed on Windows before they can be deleted. Therefore we keep track of all open files, and closes them before we delete them. Also we don't set finished blobs to read-only on Windows, since that prevents us from deleting them.
This commit is contained in:
parent
3804bc7493
commit
7c84d810d3
1 changed files with 64 additions and 8 deletions
|
@ -7,7 +7,9 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"sort"
|
"sort"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/restic/restic/backend"
|
"github.com/restic/restic/backend"
|
||||||
)
|
)
|
||||||
|
@ -16,6 +18,8 @@ var ErrWrongData = errors.New("wrong data returned by backend, checksum does not
|
||||||
|
|
||||||
type Local struct {
|
type Local struct {
|
||||||
p string
|
p string
|
||||||
|
mu sync.Mutex
|
||||||
|
open map[string][]*os.File // Contains open files. Guarded by 'mu'.
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open opens the local backend at dir.
|
// Open opens the local backend at dir.
|
||||||
|
@ -37,7 +41,7 @@ func Open(dir string) (*Local, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Local{p: dir}, nil
|
return &Local{p: dir, open: make(map[string][]*os.File)}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create creates all the necessary files and directories for a new local
|
// Create creates all the necessary files and directories for a new local
|
||||||
|
@ -143,7 +147,16 @@ func (lb *localBlob) Finalize(t backend.Type, name string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return os.Chmod(f, fi.Mode()&os.FileMode(^uint32(0222)))
|
// set file to readonly, except on Windows,
|
||||||
|
// otherwise deletion will fail.
|
||||||
|
if runtime.GOOS != "windows" {
|
||||||
|
err = os.Chmod(f, fi.Mode()&os.FileMode(^uint32(0222)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create creates a new Blob. The data is available only after Finalize()
|
// Create creates a new Blob. The data is available only after Finalize()
|
||||||
|
@ -162,6 +175,11 @@ func (b *Local) Create() (backend.Blob, error) {
|
||||||
basedir: b.p,
|
basedir: b.p,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
b.mu.Lock()
|
||||||
|
open, _ := b.open["blobs"]
|
||||||
|
b.open["blobs"] = append(open, file)
|
||||||
|
b.mu.Unlock()
|
||||||
|
|
||||||
return &blob, nil
|
return &blob, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -198,7 +216,15 @@ func dirname(base string, t backend.Type, name string) string {
|
||||||
// Get returns a reader that yields the content stored under the given
|
// Get returns a reader that yields the content stored under the given
|
||||||
// name. The reader should be closed after draining it.
|
// name. The reader should be closed after draining it.
|
||||||
func (b *Local) Get(t backend.Type, name string) (io.ReadCloser, error) {
|
func (b *Local) Get(t backend.Type, name string) (io.ReadCloser, error) {
|
||||||
return os.Open(filename(b.p, t, name))
|
file, err := os.Open(filename(b.p, t, name))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
b.mu.Lock()
|
||||||
|
open, _ := b.open[filename(b.p, t, name)]
|
||||||
|
b.open[filename(b.p, t, name)] = append(open, file)
|
||||||
|
b.mu.Unlock()
|
||||||
|
return file, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetReader returns an io.ReadCloser for the Blob with the given name of
|
// GetReader returns an io.ReadCloser for the Blob with the given name of
|
||||||
|
@ -209,6 +235,11 @@ func (b *Local) GetReader(t backend.Type, name string, offset, length uint) (io.
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
b.mu.Lock()
|
||||||
|
open, _ := b.open[filename(b.p, t, name)]
|
||||||
|
b.open[filename(b.p, t, name)] = append(open, f)
|
||||||
|
b.mu.Unlock()
|
||||||
|
|
||||||
_, err = f.Seek(int64(offset), 0)
|
_, err = f.Seek(int64(offset), 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -236,7 +267,17 @@ func (b *Local) Test(t backend.Type, name string) (bool, error) {
|
||||||
|
|
||||||
// Remove removes the blob with the given name and type.
|
// Remove removes the blob with the given name and type.
|
||||||
func (b *Local) Remove(t backend.Type, name string) error {
|
func (b *Local) Remove(t backend.Type, name string) error {
|
||||||
return os.Remove(filename(b.p, t, name))
|
// close all open files we may have.
|
||||||
|
fn := filename(b.p, t, name)
|
||||||
|
b.mu.Lock()
|
||||||
|
open, _ := b.open[fn]
|
||||||
|
for _, file := range open {
|
||||||
|
file.Close()
|
||||||
|
}
|
||||||
|
b.open[fn] = nil
|
||||||
|
b.mu.Unlock()
|
||||||
|
|
||||||
|
return os.Remove(fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
||||||
|
@ -283,7 +324,22 @@ func (b *Local) List(t backend.Type, done <-chan struct{}) <-chan string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete removes the repository and all files.
|
// Delete removes the repository and all files.
|
||||||
func (b *Local) Delete() error { return os.RemoveAll(b.p) }
|
func (b *Local) Delete() error {
|
||||||
|
b.Close()
|
||||||
|
return os.RemoveAll(b.p)
|
||||||
|
}
|
||||||
|
|
||||||
// Close does nothing
|
// Close closes all open files.
|
||||||
func (b *Local) Close() error { return nil }
|
// They may have been closed already,
|
||||||
|
// so we ignore all errors.
|
||||||
|
func (b *Local) Close() error {
|
||||||
|
b.mu.Lock()
|
||||||
|
for _, open := range b.open {
|
||||||
|
for _, file := range open {
|
||||||
|
file.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b.open = make(map[string][]*os.File)
|
||||||
|
b.mu.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue