github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/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 "os" 10 "path/filepath" 11 "sort" 12 "strings" 13 14 "github.com/juju/errors" 15 "github.com/juju/utils/v3" 16 17 "github.com/juju/juju/environs/storage" 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 = errors.NewNotFound(err, "") 64 } 65 return nil, err 66 } else if fi.IsDir() { 67 return nil, errors.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 utils.MakeFileURL(filepath.Join(f.path, name)), nil 112 } 113 114 // DefaultConsistencyStrategy implements storage.StorageReader.ConsistencyStrategy. 115 func (f *fileStorageReader) DefaultConsistencyStrategy() utils.AttemptStrategy { 116 // TODO(katco): 2016-08-09: lp:1611427 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 := os.CreateTemp(tmpdir, "juju-filestorage-") 159 if err != nil { 160 return err 161 } 162 defer func() { _ = file.Close() }() 163 164 _, err = io.CopyN(file, r, length) 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 }