github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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 // TODO(katco): 2016-08-09: lp:1611427 118 return utils.AttemptStrategy{} 119 } 120 121 // ShouldRetry is specified in the StorageReader interface. 122 func (f *fileStorageReader) ShouldRetry(err error) bool { 123 return false 124 } 125 126 type fileStorageWriter struct { 127 fileStorageReader 128 } 129 130 // NewFileStorageWriter returns a new read/write storag for 131 // a directory inside the local file system. 132 func NewFileStorageWriter(path string) (storage.Storage, error) { 133 reader, err := NewFileStorageReader(path) 134 if err != nil { 135 return nil, err 136 } 137 return &fileStorageWriter{*reader.(*fileStorageReader)}, nil 138 } 139 140 func (f *fileStorageWriter) Put(name string, r io.Reader, length int64) error { 141 if isInternalPath(name) { 142 return &os.PathError{ 143 Op: "Put", 144 Path: name, 145 Err: os.ErrPermission, 146 } 147 } 148 fullpath := f.fullPath(name) 149 dir := filepath.Dir(fullpath) 150 if err := os.MkdirAll(dir, 0755); err != nil { 151 return err 152 } 153 tmpdir := filepath.Join(f.path, ".tmp") 154 if err := os.MkdirAll(tmpdir, 0755); err != nil { 155 return err 156 } 157 defer os.Remove(tmpdir) 158 // Write to a temporary file first, and then move (atomically). 159 file, err := ioutil.TempFile(tmpdir, "juju-filestorage-") 160 if err != nil { 161 return err 162 } 163 _, err = io.CopyN(file, r, length) 164 file.Close() 165 if err != nil { 166 os.Remove(file.Name()) 167 return err 168 } 169 return utils.ReplaceFile(file.Name(), fullpath) 170 } 171 172 func (f *fileStorageWriter) Remove(name string) error { 173 fullpath := f.fullPath(name) 174 err := os.Remove(fullpath) 175 if os.IsNotExist(err) { 176 err = nil 177 } 178 return err 179 } 180 181 func (f *fileStorageWriter) RemoveAll() error { 182 return storage.RemoveAll(f) 183 }