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 }