github.com/rogpeppe/juju@v0.0.0-20140613142852-6337964b789e/environs/filestorage/filestorage.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package filestorage
     5  
     6  import (
     7  	"fmt"
     8  	"io"
     9  	"io/ioutil"
    10  	"os"
    11  	"path/filepath"
    12  	"sort"
    13  	"strings"
    14  
    15  	"github.com/juju/errors"
    16  	"github.com/juju/utils"
    17  
    18  	"github.com/juju/juju/environs/storage"
    19  )
    20  
    21  // fileStorageReader implements StorageReader backed
    22  // by the local filesystem.
    23  type fileStorageReader struct {
    24  	path string
    25  }
    26  
    27  // NewFileStorageReader returns a new storage reader for
    28  // a directory inside the local file system.
    29  func NewFileStorageReader(path string) (reader storage.StorageReader, err error) {
    30  	var p string
    31  	if p, err = utils.NormalizePath(path); err != nil {
    32  		return nil, err
    33  	}
    34  	if p, err = filepath.Abs(p); err != nil {
    35  		return nil, err
    36  	}
    37  	fi, err := os.Stat(p)
    38  	if err != nil {
    39  		return nil, err
    40  	}
    41  	if !fi.Mode().IsDir() {
    42  		return nil, fmt.Errorf("specified source path is not a directory: %s", path)
    43  	}
    44  	return &fileStorageReader{p}, nil
    45  }
    46  
    47  func (f *fileStorageReader) fullPath(name string) string {
    48  	return filepath.Join(f.path, name)
    49  }
    50  
    51  // Get implements storage.StorageReader.Get.
    52  func (f *fileStorageReader) Get(name string) (io.ReadCloser, error) {
    53  	if isInternalPath(name) {
    54  		return nil, &os.PathError{
    55  			Op:   "Get",
    56  			Path: name,
    57  			Err:  os.ErrNotExist,
    58  		}
    59  	}
    60  	filename := f.fullPath(name)
    61  	fi, err := os.Stat(filename)
    62  	if err != nil {
    63  		if os.IsNotExist(err) {
    64  			err = errors.NewNotFound(err, "")
    65  		}
    66  		return nil, err
    67  	} else if fi.IsDir() {
    68  		return nil, errors.NotFoundf("no such file with name %q", name)
    69  	}
    70  	file, err := os.Open(filename)
    71  	if err != nil {
    72  		return nil, err
    73  	}
    74  	return file, nil
    75  }
    76  
    77  // isInternalPath returns true if a path should be hidden from user visibility
    78  // filestorage uses ".tmp/" as a staging directory for uploads, so we don't
    79  // want it to be visible
    80  func isInternalPath(path string) bool {
    81  	// This blocks both ".tmp", ".tmp/foo" but also ".tmpdir", better to be
    82  	// overly restrictive to start with
    83  	return strings.HasPrefix(path, ".tmp")
    84  }
    85  
    86  // List implements storage.StorageReader.List.
    87  func (f *fileStorageReader) List(prefix string) ([]string, error) {
    88  	var names []string
    89  	if isInternalPath(prefix) {
    90  		return names, nil
    91  	}
    92  	prefix = filepath.Join(f.path, prefix)
    93  	dir := filepath.Dir(prefix)
    94  	err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
    95  		if err != nil {
    96  			return err
    97  		}
    98  		if !info.IsDir() && strings.HasPrefix(path, prefix) {
    99  			names = append(names, path[len(f.path)+1:])
   100  		}
   101  		return nil
   102  	})
   103  	if err != nil && !os.IsNotExist(err) {
   104  		return nil, err
   105  	}
   106  	sort.Strings(names)
   107  	return names, nil
   108  }
   109  
   110  // URL implements storage.StorageReader.URL.
   111  func (f *fileStorageReader) URL(name string) (string, error) {
   112  	return "file://" + filepath.Join(f.path, name), nil
   113  }
   114  
   115  // DefaultConsistencyStrategy implements storage.StorageReader.ConsistencyStrategy.
   116  func (f *fileStorageReader) DefaultConsistencyStrategy() utils.AttemptStrategy {
   117  	return utils.AttemptStrategy{}
   118  }
   119  
   120  // ShouldRetry is specified in the StorageReader interface.
   121  func (f *fileStorageReader) ShouldRetry(err error) bool {
   122  	return false
   123  }
   124  
   125  type fileStorageWriter struct {
   126  	fileStorageReader
   127  }
   128  
   129  // NewFileStorageWriter returns a new read/write storag for
   130  // a directory inside the local file system.
   131  func NewFileStorageWriter(path string) (storage.Storage, error) {
   132  	reader, err := NewFileStorageReader(path)
   133  	if err != nil {
   134  		return nil, err
   135  	}
   136  	return &fileStorageWriter{*reader.(*fileStorageReader)}, nil
   137  }
   138  
   139  func (f *fileStorageWriter) Put(name string, r io.Reader, length int64) error {
   140  	if isInternalPath(name) {
   141  		return &os.PathError{
   142  			Op:   "Put",
   143  			Path: name,
   144  			Err:  os.ErrPermission,
   145  		}
   146  	}
   147  	fullpath := f.fullPath(name)
   148  	dir := filepath.Dir(fullpath)
   149  	if err := os.MkdirAll(dir, 0755); err != nil {
   150  		return err
   151  	}
   152  	tmpdir := filepath.Join(f.path, ".tmp")
   153  	if err := os.MkdirAll(tmpdir, 0755); err != nil {
   154  		return err
   155  	}
   156  	defer os.Remove(tmpdir)
   157  	// Write to a temporary file first, and then move (atomically).
   158  	file, err := ioutil.TempFile(tmpdir, "juju-filestorage-")
   159  	if err != nil {
   160  		return err
   161  	}
   162  	_, err = io.CopyN(file, r, length)
   163  	file.Close()
   164  	if err != nil {
   165  		os.Remove(file.Name())
   166  		return err
   167  	}
   168  	return utils.ReplaceFile(file.Name(), fullpath)
   169  }
   170  
   171  func (f *fileStorageWriter) Remove(name string) error {
   172  	fullpath := f.fullPath(name)
   173  	err := os.Remove(fullpath)
   174  	if os.IsNotExist(err) {
   175  		err = nil
   176  	}
   177  	return err
   178  }
   179  
   180  func (f *fileStorageWriter) RemoveAll() error {
   181  	return storage.RemoveAll(f)
   182  }