github.com/fawick/restic@v0.1.1-0.20171126184616-c02923fbfc79/internal/cache/file.go (about)

     1  package cache
     2  
     3  import (
     4  	"io"
     5  	"os"
     6  	"path/filepath"
     7  
     8  	"github.com/pkg/errors"
     9  	"github.com/restic/restic/internal/crypto"
    10  	"github.com/restic/restic/internal/debug"
    11  	"github.com/restic/restic/internal/fs"
    12  	"github.com/restic/restic/internal/restic"
    13  )
    14  
    15  func (c *Cache) filename(h restic.Handle) string {
    16  	if len(h.Name) < 2 {
    17  		panic("Name is empty or too short")
    18  	}
    19  	subdir := h.Name[:2]
    20  	return filepath.Join(c.Path, cacheLayoutPaths[h.Type], subdir, h.Name)
    21  }
    22  
    23  func (c *Cache) canBeCached(t restic.FileType) bool {
    24  	if c == nil {
    25  		return false
    26  	}
    27  
    28  	if _, ok := cacheLayoutPaths[t]; !ok {
    29  		return false
    30  	}
    31  
    32  	return true
    33  }
    34  
    35  type readCloser struct {
    36  	io.Reader
    37  	io.Closer
    38  }
    39  
    40  // Load returns a reader that yields the contents of the file with the
    41  // given handle. rd must be closed after use. If an error is returned, the
    42  // ReadCloser is nil.
    43  func (c *Cache) Load(h restic.Handle, length int, offset int64) (io.ReadCloser, error) {
    44  	debug.Log("Load from cache: %v", h)
    45  	if !c.canBeCached(h.Type) {
    46  		return nil, errors.New("cannot be cached")
    47  	}
    48  
    49  	f, err := fs.Open(c.filename(h))
    50  	if err != nil {
    51  		return nil, errors.Wrap(err, "Open")
    52  	}
    53  
    54  	fi, err := f.Stat()
    55  	if err != nil {
    56  		_ = f.Close()
    57  		return nil, errors.Wrap(err, "Stat")
    58  	}
    59  
    60  	if fi.Size() <= crypto.Extension {
    61  		_ = f.Close()
    62  		_ = c.Remove(h)
    63  		return nil, errors.Errorf("cached file %v is truncated, removing", h)
    64  	}
    65  
    66  	if offset > 0 {
    67  		if _, err = f.Seek(offset, io.SeekStart); err != nil {
    68  			_ = f.Close()
    69  			return nil, err
    70  		}
    71  	}
    72  
    73  	rd := readCloser{Reader: f, Closer: f}
    74  	if length > 0 {
    75  		rd.Reader = io.LimitReader(f, int64(length))
    76  	}
    77  
    78  	return rd, nil
    79  }
    80  
    81  // SaveWriter returns a writer for the cache object h. It must be closed after writing is finished.
    82  func (c *Cache) SaveWriter(h restic.Handle) (io.WriteCloser, error) {
    83  	debug.Log("Save to cache: %v", h)
    84  	if !c.canBeCached(h.Type) {
    85  		return nil, errors.New("cannot be cached")
    86  	}
    87  
    88  	p := c.filename(h)
    89  	err := fs.MkdirAll(filepath.Dir(p), 0700)
    90  	if err != nil {
    91  		return nil, errors.Wrap(err, "MkdirAll")
    92  	}
    93  
    94  	f, err := fs.OpenFile(p, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0400)
    95  	if err != nil {
    96  		return nil, errors.Wrap(err, "Create")
    97  	}
    98  
    99  	return f, err
   100  }
   101  
   102  // Save saves a file in the cache.
   103  func (c *Cache) Save(h restic.Handle, rd io.Reader) error {
   104  	debug.Log("Save to cache: %v", h)
   105  	if rd == nil {
   106  		return errors.New("Save() called with nil reader")
   107  	}
   108  
   109  	f, err := c.SaveWriter(h)
   110  	if err != nil {
   111  		return err
   112  	}
   113  
   114  	n, err := io.Copy(f, rd)
   115  	if err != nil {
   116  		_ = f.Close()
   117  		_ = c.Remove(h)
   118  		return errors.Wrap(err, "Copy")
   119  	}
   120  
   121  	if n <= crypto.Extension {
   122  		_ = f.Close()
   123  		_ = c.Remove(h)
   124  		return errors.Errorf("trying to cache truncated file %v", h)
   125  	}
   126  
   127  	if err = f.Close(); err != nil {
   128  		_ = c.Remove(h)
   129  		return errors.Wrap(err, "Close")
   130  	}
   131  
   132  	return nil
   133  }
   134  
   135  // Remove deletes a file. When the file is not cache, no error is returned.
   136  func (c *Cache) Remove(h restic.Handle) error {
   137  	if !c.Has(h) {
   138  		return nil
   139  	}
   140  
   141  	return fs.Remove(c.filename(h))
   142  }
   143  
   144  // Clear removes all files of type t from the cache that are not contained in
   145  // the set valid.
   146  func (c *Cache) Clear(t restic.FileType, valid restic.IDSet) error {
   147  	debug.Log("Clearing cache for %v: %v valid files", t, len(valid))
   148  	if !c.canBeCached(t) {
   149  		return nil
   150  	}
   151  
   152  	list, err := c.list(t)
   153  	if err != nil {
   154  		return err
   155  	}
   156  
   157  	for id := range list {
   158  		if valid.Has(id) {
   159  			continue
   160  		}
   161  
   162  		if err = fs.Remove(c.filename(restic.Handle{Type: t, Name: id.String()})); err != nil {
   163  			return err
   164  		}
   165  	}
   166  
   167  	return nil
   168  }
   169  
   170  func isFile(fi os.FileInfo) bool {
   171  	return fi.Mode()&(os.ModeType|os.ModeCharDevice) == 0
   172  }
   173  
   174  // list returns a list of all files of type T in the cache.
   175  func (c *Cache) list(t restic.FileType) (restic.IDSet, error) {
   176  	if !c.canBeCached(t) {
   177  		return nil, errors.New("cannot be cached")
   178  	}
   179  
   180  	list := restic.NewIDSet()
   181  	dir := filepath.Join(c.Path, cacheLayoutPaths[t])
   182  	err := filepath.Walk(dir, func(name string, fi os.FileInfo, err error) error {
   183  		if err != nil {
   184  			return errors.Wrap(err, "Walk")
   185  		}
   186  
   187  		if !isFile(fi) {
   188  			return nil
   189  		}
   190  
   191  		id, err := restic.ParseID(filepath.Base(name))
   192  		if err != nil {
   193  			return nil
   194  		}
   195  
   196  		list.Insert(id)
   197  		return nil
   198  	})
   199  
   200  	return list, err
   201  }
   202  
   203  // Has returns true if the file is cached.
   204  func (c *Cache) Has(h restic.Handle) bool {
   205  	if !c.canBeCached(h.Type) {
   206  		return false
   207  	}
   208  
   209  	_, err := fs.Stat(c.filename(h))
   210  	if err == nil {
   211  		return true
   212  	}
   213  
   214  	return false
   215  }