github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/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 utils.MakeFileURL(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 }