2
2
mirror of https://github.com/octoleo/restic.git synced 2024-11-02 19:49:44 +00:00
restic/src/restic/backend/local/local.go

248 lines
5.3 KiB
Go
Raw Normal View History

2015-03-28 10:50:23 +00:00
package local
import (
"io"
"os"
"path/filepath"
2016-08-31 20:39:36 +00:00
"restic"
2015-03-28 10:50:23 +00:00
2016-09-01 20:17:37 +00:00
"restic/errors"
"restic/backend"
"restic/debug"
"restic/fs"
2015-03-28 10:50:23 +00:00
)
2016-01-24 19:23:50 +00:00
// Local is a backend in a local directory.
2015-03-28 10:50:23 +00:00
type Local struct {
2017-03-25 12:20:03 +00:00
Config
backend.Layout
2015-03-28 10:50:23 +00:00
}
2017-03-26 20:14:00 +00:00
// ensure statically that *Local implements restic.Backend.
2016-08-31 20:39:36 +00:00
var _ restic.Backend = &Local{}
2017-04-02 17:54:11 +00:00
const defaultLayout = "default"
2016-01-26 21:09:29 +00:00
// Open opens the local backend as specified by config.
2017-03-25 12:20:03 +00:00
func Open(cfg Config) (*Local, error) {
2017-04-02 17:54:11 +00:00
debug.Log("open local backend at %v (layout %q)", cfg.Path, cfg.Layout)
l, err := backend.ParseLayout(&backend.LocalFilesystem{}, cfg.Layout, defaultLayout, cfg.Path)
2017-04-02 15:57:28 +00:00
if err != nil {
return nil, err
}
2017-04-02 15:57:28 +00:00
be := &Local{Config: cfg, Layout: l}
return be, nil
2015-03-28 10:50:23 +00:00
}
// Create creates all the necessary files and directories for a new local
2015-05-04 18:39:45 +00:00
// backend at dir. Afterwards a new config blob should be created.
2017-03-25 12:20:03 +00:00
func Create(cfg Config) (*Local, error) {
2017-04-02 17:54:11 +00:00
debug.Log("create local backend at %v (layout %q)", cfg.Path, cfg.Layout)
l, err := backend.ParseLayout(&backend.LocalFilesystem{}, cfg.Layout, defaultLayout, cfg.Path)
if err != nil {
return nil, err
}
be := &Local{
Config: cfg,
2017-04-02 17:54:11 +00:00
Layout: l,
}
2015-05-04 18:39:45 +00:00
// test if config file already exists
2017-04-02 17:54:11 +00:00
_, err = fs.Lstat(be.Filename(restic.Handle{Type: restic.ConfigFile}))
2015-03-28 10:50:23 +00:00
if err == nil {
return nil, errors.New("config file already exists")
2015-03-28 10:50:23 +00:00
}
2017-04-19 16:56:01 +00:00
// create paths for data and refs
for _, d := range be.Paths() {
err := fs.MkdirAll(d, backend.Modes.Dir)
2015-03-28 10:50:23 +00:00
if err != nil {
2016-08-29 19:54:50 +00:00
return nil, errors.Wrap(err, "MkdirAll")
2015-03-28 10:50:23 +00:00
}
}
return be, nil
2015-03-28 10:50:23 +00:00
}
// Location returns this backend's location (the directory name).
func (b *Local) Location() string {
2017-03-25 12:20:03 +00:00
return b.Path
2015-03-28 10:50:23 +00:00
}
// Save stores data in the backend at the handle.
func (b *Local) Save(h restic.Handle, rd io.Reader) (err error) {
debug.Log("Save %v", h)
if err := h.Valid(); err != nil {
2016-01-24 15:59:38 +00:00
return err
}
filename := b.Filename(h)
2016-01-24 15:59:38 +00:00
// create directories if necessary, ignore errors
2016-09-01 19:19:30 +00:00
if h.Type == restic.DataFile {
err = fs.MkdirAll(filepath.Dir(filename), backend.Modes.Dir)
if err != nil {
2016-08-29 19:54:50 +00:00
return errors.Wrap(err, "MkdirAll")
}
}
// create new file
f, err := fs.OpenFile(filename, os.O_CREATE|os.O_EXCL|os.O_WRONLY, backend.Modes.File)
if err != nil {
return errors.Wrap(err, "OpenFile")
}
// save data, then sync
_, err = io.Copy(f, rd)
if err != nil {
f.Close()
return errors.Wrap(err, "Write")
}
if err = f.Sync(); err != nil {
f.Close()
return errors.Wrap(err, "Sync")
}
2016-01-24 15:59:38 +00:00
err = f.Close()
2016-01-24 15:59:38 +00:00
if err != nil {
return errors.Wrap(err, "Close")
2016-01-24 15:59:38 +00:00
}
// set mode to read-only
fi, err := fs.Stat(filename)
2016-01-24 15:59:38 +00:00
if err != nil {
2016-08-29 19:54:50 +00:00
return errors.Wrap(err, "Stat")
2016-01-24 15:59:38 +00:00
}
return setNewFileMode(filename, fi)
2016-01-24 00:15:35 +00:00
}
2017-01-23 17:11:10 +00:00
// Load returns a reader that yields the contents of the file at h at the
2017-01-22 21:01:12 +00:00
// given offset. If length is nonzero, only a portion of the file is
// returned. rd must be closed after use.
2017-01-23 17:11:10 +00:00
func (b *Local) Load(h restic.Handle, length int, offset int64) (io.ReadCloser, error) {
debug.Log("Load %v, length %v, offset %v", h, length, offset)
2017-01-22 21:01:12 +00:00
if err := h.Valid(); err != nil {
return nil, err
}
if offset < 0 {
return nil, errors.New("offset is negative")
}
f, err := fs.Open(b.Filename(h))
2017-01-22 21:01:12 +00:00
if err != nil {
return nil, err
}
if offset > 0 {
_, err = f.Seek(offset, 0)
if err != nil {
f.Close()
return nil, err
}
}
if length > 0 {
return backend.LimitReadCloser(f, int64(length)), nil
}
return f, nil
}
2016-01-23 22:27:58 +00:00
// Stat returns information about a blob.
2016-08-31 20:39:36 +00:00
func (b *Local) Stat(h restic.Handle) (restic.FileInfo, error) {
2016-09-27 20:35:08 +00:00
debug.Log("Stat %v", h)
2016-01-23 22:27:58 +00:00
if err := h.Valid(); err != nil {
2016-08-31 20:39:36 +00:00
return restic.FileInfo{}, err
2016-01-23 22:27:58 +00:00
}
fi, err := fs.Stat(b.Filename(h))
2016-01-23 22:27:58 +00:00
if err != nil {
2016-08-31 20:39:36 +00:00
return restic.FileInfo{}, errors.Wrap(err, "Stat")
2016-01-23 22:27:58 +00:00
}
2016-08-31 20:39:36 +00:00
return restic.FileInfo{Size: fi.Size()}, nil
2016-01-23 22:27:58 +00:00
}
2015-03-28 10:50:23 +00:00
// Test returns true if a blob of the given type and name exists in the backend.
func (b *Local) Test(h restic.Handle) (bool, error) {
debug.Log("Test %v", h)
_, err := fs.Stat(b.Filename(h))
2015-03-28 10:50:23 +00:00
if err != nil {
if os.IsNotExist(errors.Cause(err)) {
2015-03-28 10:50:23 +00:00
return false, nil
}
2016-08-29 19:54:50 +00:00
return false, errors.Wrap(err, "Stat")
2015-03-28 10:50:23 +00:00
}
return true, nil
}
// Remove removes the blob with the given name and type.
func (b *Local) Remove(h restic.Handle) error {
debug.Log("Remove %v", h)
fn := b.Filename(h)
2015-08-19 20:02:47 +00:00
// reset read-only flag
err := fs.Chmod(fn, 0666)
2015-08-19 20:02:47 +00:00
if err != nil {
2016-08-29 19:54:50 +00:00
return errors.Wrap(err, "Chmod")
2015-08-19 20:02:47 +00:00
}
return fs.Remove(fn)
2015-03-28 10:50:23 +00:00
}
func isFile(fi os.FileInfo) bool {
return fi.Mode()&(os.ModeType|os.ModeCharDevice) == 0
}
2015-03-28 10:50:23 +00:00
// List returns a channel that yields all names of blobs of type t. A
2015-06-28 07:44:06 +00:00
// goroutine is started for this. If the channel done is closed, sending
2015-03-28 10:50:23 +00:00
// stops.
2016-08-31 20:39:36 +00:00
func (b *Local) List(t restic.FileType, done <-chan struct{}) <-chan string {
2016-09-27 20:35:08 +00:00
debug.Log("List %v", t)
2015-03-28 10:50:23 +00:00
ch := make(chan string)
go func() {
defer close(ch)
2017-04-10 21:31:13 +00:00
fs.Walk(b.Basedir(t), func(path string, fi os.FileInfo, err error) error {
if !isFile(fi) {
return err
2015-03-28 10:50:23 +00:00
}
select {
2017-04-10 21:31:13 +00:00
case ch <- filepath.Base(path):
2015-03-28 10:50:23 +00:00
case <-done:
2017-04-10 21:31:13 +00:00
return err
2015-03-28 10:50:23 +00:00
}
2017-04-10 21:31:13 +00:00
return err
})
2015-03-28 10:50:23 +00:00
}()
return ch
}
// Delete removes the repository and all files.
func (b *Local) Delete() error {
2016-09-27 20:35:08 +00:00
debug.Log("Delete()")
2017-03-25 12:20:03 +00:00
return fs.RemoveAll(b.Path)
}
2015-03-28 10:50:23 +00:00
// Close closes all open files.
func (b *Local) Close() error {
2016-09-27 20:35:08 +00:00
debug.Log("Close()")
// this does not need to do anything, all open files are closed within the
// same function.
return nil
}