github.com/cs3org/reva/v2@v2.27.7/pkg/storage/utils/decomposedfs/metadata/xattrs_backend.go (about) 1 // Copyright 2018-2023 CERN 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 // 15 // In applying this license, CERN does not waive the privileges and immunities 16 // granted to it by virtue of its status as an Intergovernmental Organization 17 // or submit itself to any jurisdiction. 18 19 package metadata 20 21 import ( 22 "context" 23 "io" 24 "os" 25 "path/filepath" 26 "strconv" 27 "strings" 28 29 "github.com/cs3org/reva/v2/pkg/storage/cache" 30 "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/metadata/prefixes" 31 "github.com/cs3org/reva/v2/pkg/storage/utils/filelocks" 32 "github.com/pkg/errors" 33 "github.com/pkg/xattr" 34 "github.com/rogpeppe/go-internal/lockedfile" 35 ) 36 37 // XattrsBackend stores the file attributes in extended attributes 38 type XattrsBackend struct { 39 rootPath string 40 metaCache cache.FileMetadataCache 41 } 42 43 // NewMessageBackend returns a new XattrsBackend instance 44 func NewXattrsBackend(rootPath string, o cache.Config) XattrsBackend { 45 return XattrsBackend{ 46 metaCache: cache.GetFileMetadataCache(o), 47 } 48 } 49 50 // Name returns the name of the backend 51 func (XattrsBackend) Name() string { return "xattrs" } 52 53 // Get an extended attribute value for the given key 54 // No file locking is involved here as reading a single xattr is 55 // considered to be atomic. 56 func (b XattrsBackend) Get(ctx context.Context, path, key string) ([]byte, error) { 57 attribs := map[string][]byte{} 58 err := b.metaCache.PullFromCache(b.cacheKey(path), &attribs) 59 if err == nil && len(attribs[key]) > 0 { 60 return attribs[key], err 61 } 62 63 return xattr.Get(path, key) 64 } 65 66 // GetInt64 reads a string as int64 from the xattrs 67 func (b XattrsBackend) GetInt64(ctx context.Context, filePath, key string) (int64, error) { 68 attr, err := b.Get(ctx, filePath, key) 69 if err != nil { 70 return 0, err 71 } 72 v, err := strconv.ParseInt(string(attr), 10, 64) 73 if err != nil { 74 return 0, err 75 } 76 return v, nil 77 } 78 79 // List retrieves a list of names of extended attributes associated with the 80 // given path in the file system. 81 func (b XattrsBackend) List(ctx context.Context, filePath string) (attribs []string, err error) { 82 return b.list(ctx, filePath, true) 83 } 84 85 func (b XattrsBackend) list(ctx context.Context, filePath string, acquireLock bool) (attribs []string, err error) { 86 attrs, err := xattr.List(filePath) 87 if err == nil { 88 return attrs, nil 89 } 90 91 // listing xattrs failed, try again, either with lock or without 92 if acquireLock { 93 f, err := lockedfile.OpenFile(filePath+filelocks.LockFileSuffix, os.O_CREATE|os.O_WRONLY, 0600) 94 if err != nil { 95 return nil, err 96 } 97 defer cleanupLockfile(ctx, f) 98 99 } 100 return xattr.List(filePath) 101 } 102 103 // All reads all extended attributes for a node, protected by a 104 // shared file lock 105 func (b XattrsBackend) All(ctx context.Context, path string) (map[string][]byte, error) { 106 return b.getAll(ctx, path, false, true) 107 } 108 109 func (b XattrsBackend) getAll(ctx context.Context, path string, skipCache, acquireLock bool) (map[string][]byte, error) { 110 attribs := map[string][]byte{} 111 112 if !skipCache { 113 err := b.metaCache.PullFromCache(b.cacheKey(path), &attribs) 114 if err == nil { 115 return attribs, err 116 } 117 } 118 119 attrNames, err := b.list(ctx, path, acquireLock) 120 if err != nil { 121 return nil, err 122 } 123 124 if len(attrNames) == 0 { 125 return attribs, nil 126 } 127 128 var ( 129 xerrs = 0 130 xerr error 131 ) 132 // error handling: Count if there are errors while reading all attribs. 133 // if there were any, return an error. 134 attribs = make(map[string][]byte, len(attrNames)) 135 for _, name := range attrNames { 136 var val []byte 137 if val, xerr = xattr.Get(path, name); xerr != nil && !IsAttrUnset(xerr) { 138 xerrs++ 139 } else { 140 attribs[name] = val 141 } 142 } 143 144 if xerrs > 0 { 145 return nil, errors.Wrap(xerr, "Failed to read all xattrs") 146 } 147 148 err = b.metaCache.PushToCache(b.cacheKey(path), attribs) 149 if err != nil { 150 return nil, err 151 } 152 153 return attribs, nil 154 } 155 156 // Set sets one attribute for the given path 157 func (b XattrsBackend) Set(ctx context.Context, path string, key string, val []byte) (err error) { 158 return b.SetMultiple(ctx, path, map[string][]byte{key: val}, true) 159 } 160 161 // SetMultiple sets a set of attribute for the given path 162 func (b XattrsBackend) SetMultiple(ctx context.Context, path string, attribs map[string][]byte, acquireLock bool) (err error) { 163 if acquireLock { 164 err := os.MkdirAll(filepath.Dir(path), 0600) 165 if err != nil { 166 return err 167 } 168 lockedFile, err := lockedfile.OpenFile(b.LockfilePath(path), os.O_CREATE|os.O_WRONLY, 0600) 169 if err != nil { 170 return err 171 } 172 defer cleanupLockfile(ctx, lockedFile) 173 } 174 175 // error handling: Count if there are errors while setting the attribs. 176 // if there were any, return an error. 177 var ( 178 xerrs = 0 179 xerr error 180 ) 181 for key, val := range attribs { 182 if xerr = xattr.Set(path, key, val); xerr != nil { 183 // log 184 xerrs++ 185 } 186 } 187 if xerrs > 0 { 188 return errors.Wrap(xerr, "Failed to set all xattrs") 189 } 190 191 attribs, err = b.getAll(ctx, path, true, false) 192 if err != nil { 193 return err 194 } 195 return b.metaCache.PushToCache(b.cacheKey(path), attribs) 196 } 197 198 // Remove an extended attribute key 199 func (b XattrsBackend) Remove(ctx context.Context, path string, key string, acquireLock bool) error { 200 if acquireLock { 201 lockedFile, err := lockedfile.OpenFile(path+filelocks.LockFileSuffix, os.O_CREATE|os.O_WRONLY, 0600) 202 if err != nil { 203 return err 204 } 205 defer cleanupLockfile(ctx, lockedFile) 206 } 207 208 err := xattr.Remove(path, key) 209 if err != nil { 210 return err 211 } 212 attribs, err := b.getAll(ctx, path, true, false) 213 if err != nil { 214 return err 215 } 216 return b.metaCache.PushToCache(b.cacheKey(path), attribs) 217 } 218 219 // IsMetaFile returns whether the given path represents a meta file 220 func (XattrsBackend) IsMetaFile(path string) bool { return strings.HasSuffix(path, ".meta.lock") } 221 222 // Purge purges the data of a given path 223 func (b XattrsBackend) Purge(ctx context.Context, path string) error { 224 _, err := os.Stat(path) 225 if err == nil { 226 attribs, err := b.getAll(ctx, path, true, true) 227 if err != nil { 228 return err 229 } 230 231 for attr := range attribs { 232 if strings.HasPrefix(attr, prefixes.OcisPrefix) { 233 err := xattr.Remove(path, attr) 234 if err != nil { 235 return err 236 } 237 } 238 } 239 } 240 241 return b.metaCache.RemoveMetadata(b.cacheKey(path)) 242 } 243 244 // Rename moves the data for a given path to a new path 245 func (b XattrsBackend) Rename(oldPath, newPath string) error { 246 data := map[string][]byte{} 247 err := b.metaCache.PullFromCache(b.cacheKey(oldPath), &data) 248 if err == nil { 249 err = b.metaCache.PushToCache(b.cacheKey(newPath), data) 250 if err != nil { 251 return err 252 } 253 } 254 return b.metaCache.RemoveMetadata(b.cacheKey(oldPath)) 255 } 256 257 // MetadataPath returns the path of the file holding the metadata for the given path 258 func (XattrsBackend) MetadataPath(path string) string { return path } 259 260 // LockfilePath returns the path of the lock file 261 func (XattrsBackend) LockfilePath(path string) string { return path + ".mlock" } 262 263 // Lock locks the metadata for the given path 264 func (b XattrsBackend) Lock(path string) (UnlockFunc, error) { 265 metaLockPath := b.LockfilePath(path) 266 mlock, err := lockedfile.OpenFile(metaLockPath, os.O_RDWR|os.O_CREATE, 0600) 267 if err != nil { 268 return nil, err 269 } 270 return func() error { 271 err := mlock.Close() 272 if err != nil { 273 return err 274 } 275 return os.Remove(metaLockPath) 276 }, nil 277 } 278 279 func cleanupLockfile(ctx context.Context, f *lockedfile.File) { 280 _ = f.Close() 281 _ = os.Remove(f.Name()) 282 } 283 284 // AllWithLockedSource reads all extended attributes from the given reader. 285 // The path argument is used for storing the data in the cache 286 func (b XattrsBackend) AllWithLockedSource(ctx context.Context, path string, _ io.Reader) (map[string][]byte, error) { 287 return b.All(ctx, path) 288 } 289 290 func (b XattrsBackend) cacheKey(path string) string { 291 // rootPath is guaranteed to have no trailing slash 292 // the cache key shouldn't begin with a slash as some stores drop it which can cause 293 // confusion 294 return strings.TrimPrefix(path, b.rootPath+"/") 295 }