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