github.com/mckael/restic@v0.8.3/internal/backend/local/local.go (about)

     1  package local
     2  
     3  import (
     4  	"context"
     5  	"io"
     6  	"os"
     7  	"path/filepath"
     8  
     9  	"github.com/restic/restic/internal/errors"
    10  	"github.com/restic/restic/internal/restic"
    11  
    12  	"github.com/restic/restic/internal/backend"
    13  	"github.com/restic/restic/internal/debug"
    14  	"github.com/restic/restic/internal/fs"
    15  )
    16  
    17  // Local is a backend in a local directory.
    18  type Local struct {
    19  	Config
    20  	backend.Layout
    21  }
    22  
    23  // ensure statically that *Local implements restic.Backend.
    24  var _ restic.Backend = &Local{}
    25  
    26  const defaultLayout = "default"
    27  
    28  // dirExists returns true if the name exists and is a directory.
    29  func dirExists(name string) bool {
    30  	f, err := fs.Open(name)
    31  	if err != nil {
    32  		return false
    33  	}
    34  
    35  	fi, err := f.Stat()
    36  	if err != nil {
    37  		return false
    38  	}
    39  
    40  	if err = f.Close(); err != nil {
    41  		return false
    42  	}
    43  
    44  	return fi.IsDir()
    45  }
    46  
    47  // Open opens the local backend as specified by config.
    48  func Open(cfg Config) (*Local, error) {
    49  	debug.Log("open local backend at %v (layout %q)", cfg.Path, cfg.Layout)
    50  	l, err := backend.ParseLayout(&backend.LocalFilesystem{}, cfg.Layout, defaultLayout, cfg.Path)
    51  	if err != nil {
    52  		return nil, err
    53  	}
    54  
    55  	return &Local{Config: cfg, Layout: l}, nil
    56  }
    57  
    58  // Create creates all the necessary files and directories for a new local
    59  // backend at dir. Afterwards a new config blob should be created.
    60  func Create(cfg Config) (*Local, error) {
    61  	debug.Log("create local backend at %v (layout %q)", cfg.Path, cfg.Layout)
    62  
    63  	l, err := backend.ParseLayout(&backend.LocalFilesystem{}, cfg.Layout, defaultLayout, cfg.Path)
    64  	if err != nil {
    65  		return nil, err
    66  	}
    67  
    68  	be := &Local{
    69  		Config: cfg,
    70  		Layout: l,
    71  	}
    72  
    73  	// test if config file already exists
    74  	_, err = fs.Lstat(be.Filename(restic.Handle{Type: restic.ConfigFile}))
    75  	if err == nil {
    76  		return nil, errors.New("config file already exists")
    77  	}
    78  
    79  	// create paths for data and refs
    80  	for _, d := range be.Paths() {
    81  		err := fs.MkdirAll(d, backend.Modes.Dir)
    82  		if err != nil {
    83  			return nil, errors.Wrap(err, "MkdirAll")
    84  		}
    85  	}
    86  
    87  	return be, nil
    88  }
    89  
    90  // Location returns this backend's location (the directory name).
    91  func (b *Local) Location() string {
    92  	return b.Path
    93  }
    94  
    95  // IsNotExist returns true if the error is caused by a non existing file.
    96  func (b *Local) IsNotExist(err error) bool {
    97  	return os.IsNotExist(errors.Cause(err))
    98  }
    99  
   100  // Save stores data in the backend at the handle.
   101  func (b *Local) Save(ctx context.Context, h restic.Handle, rd io.Reader) error {
   102  	debug.Log("Save %v", h)
   103  	if err := h.Valid(); err != nil {
   104  		return err
   105  	}
   106  
   107  	filename := b.Filename(h)
   108  
   109  	// create new file
   110  	f, err := fs.OpenFile(filename, os.O_CREATE|os.O_EXCL|os.O_WRONLY, backend.Modes.File)
   111  
   112  	if b.IsNotExist(err) {
   113  		debug.Log("error %v: creating dir", err)
   114  
   115  		// error is caused by a missing directory, try to create it
   116  		mkdirErr := os.MkdirAll(filepath.Dir(filename), backend.Modes.Dir)
   117  		if mkdirErr != nil {
   118  			debug.Log("error creating dir %v: %v", filepath.Dir(filename), mkdirErr)
   119  		} else {
   120  			// try again
   121  			f, err = fs.OpenFile(filename, os.O_CREATE|os.O_EXCL|os.O_WRONLY, backend.Modes.File)
   122  		}
   123  	}
   124  
   125  	if err != nil {
   126  		return errors.Wrap(err, "OpenFile")
   127  	}
   128  
   129  	// save data, then sync
   130  	_, err = io.Copy(f, rd)
   131  	if err != nil {
   132  		_ = f.Close()
   133  		return errors.Wrap(err, "Write")
   134  	}
   135  
   136  	if err = f.Sync(); err != nil {
   137  		_ = f.Close()
   138  		return errors.Wrap(err, "Sync")
   139  	}
   140  
   141  	err = f.Close()
   142  	if err != nil {
   143  		return errors.Wrap(err, "Close")
   144  	}
   145  
   146  	return setNewFileMode(filename, backend.Modes.File)
   147  }
   148  
   149  // Load runs fn with a reader that yields the contents of the file at h at the
   150  // given offset.
   151  func (b *Local) Load(ctx context.Context, h restic.Handle, length int, offset int64, fn func(rd io.Reader) error) error {
   152  	return backend.DefaultLoad(ctx, h, length, offset, b.openReader, fn)
   153  }
   154  
   155  func (b *Local) openReader(ctx context.Context, h restic.Handle, length int, offset int64) (io.ReadCloser, error) {
   156  	debug.Log("Load %v, length %v, offset %v", h, length, offset)
   157  	if err := h.Valid(); err != nil {
   158  		return nil, err
   159  	}
   160  
   161  	if offset < 0 {
   162  		return nil, errors.New("offset is negative")
   163  	}
   164  
   165  	f, err := fs.Open(b.Filename(h))
   166  	if err != nil {
   167  		return nil, err
   168  	}
   169  
   170  	if offset > 0 {
   171  		_, err = f.Seek(offset, 0)
   172  		if err != nil {
   173  			_ = f.Close()
   174  			return nil, err
   175  		}
   176  	}
   177  
   178  	if length > 0 {
   179  		return backend.LimitReadCloser(f, int64(length)), nil
   180  	}
   181  
   182  	return f, nil
   183  }
   184  
   185  // Stat returns information about a blob.
   186  func (b *Local) Stat(ctx context.Context, h restic.Handle) (restic.FileInfo, error) {
   187  	debug.Log("Stat %v", h)
   188  	if err := h.Valid(); err != nil {
   189  		return restic.FileInfo{}, err
   190  	}
   191  
   192  	fi, err := fs.Stat(b.Filename(h))
   193  	if err != nil {
   194  		return restic.FileInfo{}, errors.Wrap(err, "Stat")
   195  	}
   196  
   197  	return restic.FileInfo{Size: fi.Size(), Name: h.Name}, nil
   198  }
   199  
   200  // Test returns true if a blob of the given type and name exists in the backend.
   201  func (b *Local) Test(ctx context.Context, h restic.Handle) (bool, error) {
   202  	debug.Log("Test %v", h)
   203  	_, err := fs.Stat(b.Filename(h))
   204  	if err != nil {
   205  		if os.IsNotExist(errors.Cause(err)) {
   206  			return false, nil
   207  		}
   208  		return false, errors.Wrap(err, "Stat")
   209  	}
   210  
   211  	return true, nil
   212  }
   213  
   214  // Remove removes the blob with the given name and type.
   215  func (b *Local) Remove(ctx context.Context, h restic.Handle) error {
   216  	debug.Log("Remove %v", h)
   217  	fn := b.Filename(h)
   218  
   219  	// reset read-only flag
   220  	err := fs.Chmod(fn, 0666)
   221  	if err != nil {
   222  		return errors.Wrap(err, "Chmod")
   223  	}
   224  
   225  	return fs.Remove(fn)
   226  }
   227  
   228  func isFile(fi os.FileInfo) bool {
   229  	return fi.Mode()&(os.ModeType|os.ModeCharDevice) == 0
   230  }
   231  
   232  // List runs fn for each file in the backend which has the type t. When an
   233  // error occurs (or fn returns an error), List stops and returns it.
   234  func (b *Local) List(ctx context.Context, t restic.FileType, fn func(restic.FileInfo) error) error {
   235  	debug.Log("List %v", t)
   236  
   237  	basedir, subdirs := b.Basedir(t)
   238  	return fs.Walk(basedir, func(path string, fi os.FileInfo, err error) error {
   239  		debug.Log("walk on %v\n", path)
   240  		if err != nil {
   241  			return err
   242  		}
   243  
   244  		if path == basedir {
   245  			return nil
   246  		}
   247  
   248  		if !isFile(fi) {
   249  			return nil
   250  		}
   251  
   252  		if fi.IsDir() && !subdirs {
   253  			return filepath.SkipDir
   254  		}
   255  
   256  		debug.Log("send %v\n", filepath.Base(path))
   257  
   258  		rfi := restic.FileInfo{
   259  			Name: filepath.Base(path),
   260  			Size: fi.Size(),
   261  		}
   262  
   263  		if ctx.Err() != nil {
   264  			return ctx.Err()
   265  		}
   266  
   267  		err = fn(rfi)
   268  		if err != nil {
   269  			return err
   270  		}
   271  
   272  		return ctx.Err()
   273  	})
   274  }
   275  
   276  // Delete removes the repository and all files.
   277  func (b *Local) Delete(ctx context.Context) error {
   278  	debug.Log("Delete()")
   279  	return fs.RemoveAll(b.Path)
   280  }
   281  
   282  // Close closes all open files.
   283  func (b *Local) Close() error {
   284  	debug.Log("Close()")
   285  	// this does not need to do anything, all open files are closed within the
   286  	// same function.
   287  	return nil
   288  }