github.com/cs3org/reva/v2@v2.27.7/pkg/storage/utils/decomposedfs/metadata/messagepack_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 "errors" 24 "io" 25 "io/fs" 26 "os" 27 "path/filepath" 28 "strconv" 29 "strings" 30 31 "github.com/cs3org/reva/v2/pkg/storage/cache" 32 "github.com/google/renameio/v2" 33 "github.com/pkg/xattr" 34 "github.com/rogpeppe/go-internal/lockedfile" 35 "github.com/shamaton/msgpack/v2" 36 "go.opentelemetry.io/otel/codes" 37 ) 38 39 // MessagePackBackend persists the attributes in messagepack format inside the file 40 type MessagePackBackend struct { 41 rootPath string 42 metaCache cache.FileMetadataCache 43 } 44 45 type readWriteCloseSeekTruncater interface { 46 io.ReadWriteCloser 47 io.Seeker 48 Truncate(int64) error 49 } 50 51 // NewMessagePackBackend returns a new MessagePackBackend instance 52 func NewMessagePackBackend(rootPath string, o cache.Config) MessagePackBackend { 53 return MessagePackBackend{ 54 rootPath: filepath.Clean(rootPath), 55 metaCache: cache.GetFileMetadataCache(o), 56 } 57 } 58 59 // Name returns the name of the backend 60 func (MessagePackBackend) Name() string { return "messagepack" } 61 62 // All reads all extended attributes for a node 63 func (b MessagePackBackend) All(ctx context.Context, path string) (map[string][]byte, error) { 64 return b.loadAttributes(ctx, path, nil) 65 } 66 67 // Get an extended attribute value for the given key 68 func (b MessagePackBackend) Get(ctx context.Context, path, key string) ([]byte, error) { 69 attribs, err := b.loadAttributes(ctx, path, nil) 70 if err != nil { 71 return []byte{}, err 72 } 73 val, ok := attribs[key] 74 if !ok { 75 return []byte{}, &xattr.Error{Op: "mpk.get", Path: path, Name: key, Err: xattr.ENOATTR} 76 } 77 return val, nil 78 } 79 80 // GetInt64 reads a string as int64 from the xattrs 81 func (b MessagePackBackend) GetInt64(ctx context.Context, path, key string) (int64, error) { 82 attribs, err := b.loadAttributes(ctx, path, nil) 83 if err != nil { 84 return 0, err 85 } 86 val, ok := attribs[key] 87 if !ok { 88 return 0, &xattr.Error{Op: "mpk.get", Path: path, Name: key, Err: xattr.ENOATTR} 89 } 90 i, err := strconv.ParseInt(string(val), 10, 64) 91 if err != nil { 92 return 0, err 93 } 94 return i, nil 95 } 96 97 // List retrieves a list of names of extended attributes associated with the 98 // given path in the file system. 99 func (b MessagePackBackend) List(ctx context.Context, path string) ([]string, error) { 100 attribs, err := b.loadAttributes(ctx, path, nil) 101 if err != nil { 102 return nil, err 103 } 104 keys := []string{} 105 for k := range attribs { 106 keys = append(keys, k) 107 } 108 return keys, nil 109 } 110 111 // Set sets one attribute for the given path 112 func (b MessagePackBackend) Set(ctx context.Context, path, key string, val []byte) error { 113 return b.SetMultiple(ctx, path, map[string][]byte{key: val}, true) 114 } 115 116 // SetMultiple sets a set of attribute for the given path 117 func (b MessagePackBackend) SetMultiple(ctx context.Context, path string, attribs map[string][]byte, acquireLock bool) error { 118 return b.saveAttributes(ctx, path, attribs, nil, acquireLock) 119 } 120 121 // Remove an extended attribute key 122 func (b MessagePackBackend) Remove(ctx context.Context, path, key string, acquireLock bool) error { 123 return b.saveAttributes(ctx, path, nil, []string{key}, acquireLock) 124 } 125 126 // AllWithLockedSource reads all extended attributes from the given reader (if possible). 127 // The path argument is used for storing the data in the cache 128 func (b MessagePackBackend) AllWithLockedSource(ctx context.Context, path string, source io.Reader) (map[string][]byte, error) { 129 return b.loadAttributes(ctx, path, source) 130 } 131 132 func (b MessagePackBackend) saveAttributes(ctx context.Context, path string, setAttribs map[string][]byte, deleteAttribs []string, acquireLock bool) error { 133 var ( 134 err error 135 f readWriteCloseSeekTruncater 136 ) 137 ctx, span := tracer.Start(ctx, "saveAttributes") 138 defer func() { 139 if err != nil { 140 span.SetStatus(codes.Error, err.Error()) 141 } else { 142 span.SetStatus(codes.Ok, "") 143 } 144 span.End() 145 }() 146 147 lockPath := b.LockfilePath(path) 148 metaPath := b.MetadataPath(path) 149 if acquireLock { 150 _, subspan := tracer.Start(ctx, "lockedfile.OpenFile") 151 f, err = lockedfile.OpenFile(lockPath, os.O_RDWR|os.O_CREATE, 0600) 152 subspan.End() 153 if err != nil { 154 return err 155 } 156 defer f.Close() 157 } 158 // Read current state 159 _, subspan := tracer.Start(ctx, "os.ReadFile") 160 var msgBytes []byte 161 msgBytes, err = os.ReadFile(metaPath) 162 subspan.End() 163 attribs := map[string][]byte{} 164 switch { 165 case err != nil: 166 if !errors.Is(err, fs.ErrNotExist) { 167 return err 168 } 169 case len(msgBytes) == 0: 170 // ugh. an empty file? bail out 171 return errors.New("encountered empty metadata file") 172 default: 173 // only unmarshal if we read data 174 err = msgpack.Unmarshal(msgBytes, &attribs) 175 if err != nil { 176 return err 177 } 178 } 179 180 // prepare metadata 181 for key, val := range setAttribs { 182 attribs[key] = val 183 } 184 for _, key := range deleteAttribs { 185 delete(attribs, key) 186 } 187 var d []byte 188 d, err = msgpack.Marshal(attribs) 189 if err != nil { 190 return err 191 } 192 193 // overwrite file atomically 194 _, subspan = tracer.Start(ctx, "renameio.Writefile") 195 err = renameio.WriteFile(metaPath, d, 0600) 196 if err != nil { 197 return err 198 } 199 subspan.End() 200 201 _, subspan = tracer.Start(ctx, "metaCache.PushToCache") 202 err = b.metaCache.PushToCache(b.cacheKey(path), attribs) 203 subspan.End() 204 return err 205 } 206 207 func (b MessagePackBackend) loadAttributes(ctx context.Context, path string, source io.Reader) (map[string][]byte, error) { 208 ctx, span := tracer.Start(ctx, "loadAttributes") 209 defer span.End() 210 attribs := map[string][]byte{} 211 err := b.metaCache.PullFromCache(b.cacheKey(path), &attribs) 212 if err == nil { 213 return attribs, err 214 } 215 216 metaPath := b.MetadataPath(path) 217 var msgBytes []byte 218 219 if source == nil { 220 // // No cached entry found. Read from storage and store in cache 221 _, subspan := tracer.Start(ctx, "os.OpenFile") 222 // source, err = lockedfile.Open(metaPath) 223 source, err = os.Open(metaPath) 224 subspan.End() 225 // // No cached entry found. Read from storage and store in cache 226 if err != nil { 227 if os.IsNotExist(err) { 228 // some of the caller rely on ENOTEXISTS to be returned when the 229 // actual file (not the metafile) does not exist in order to 230 // determine whether a node exists or not -> stat the actual node 231 _, subspan := tracer.Start(ctx, "os.Stat") 232 _, err := os.Stat(path) 233 subspan.End() 234 if err != nil { 235 return nil, err 236 } 237 return attribs, nil // no attributes set yet 238 } 239 } 240 _, subspan = tracer.Start(ctx, "io.ReadAll") 241 msgBytes, err = io.ReadAll(source) 242 source.(*os.File).Close() 243 subspan.End() 244 } else { 245 _, subspan := tracer.Start(ctx, "io.ReadAll") 246 msgBytes, err = io.ReadAll(source) 247 subspan.End() 248 } 249 250 if err != nil { 251 return nil, err 252 } 253 if len(msgBytes) > 0 { 254 err = msgpack.Unmarshal(msgBytes, &attribs) 255 if err != nil { 256 return nil, err 257 } 258 } 259 260 _, subspan := tracer.Start(ctx, "metaCache.PushToCache") 261 err = b.metaCache.PushToCache(b.cacheKey(path), attribs) 262 subspan.End() 263 if err != nil { 264 return nil, err 265 } 266 267 return attribs, nil 268 } 269 270 // IsMetaFile returns whether the given path represents a meta file 271 func (MessagePackBackend) IsMetaFile(path string) bool { 272 return strings.HasSuffix(path, ".mpk") || strings.HasSuffix(path, ".mlock") 273 } 274 275 // Purge purges the data of a given path 276 func (b MessagePackBackend) Purge(_ context.Context, path string) error { 277 if err := b.metaCache.RemoveMetadata(b.cacheKey(path)); err != nil { 278 return err 279 } 280 return os.Remove(b.MetadataPath(path)) 281 } 282 283 // Rename moves the data for a given path to a new path 284 func (b MessagePackBackend) Rename(oldPath, newPath string) error { 285 data := map[string][]byte{} 286 err := b.metaCache.PullFromCache(b.cacheKey(oldPath), &data) 287 if err == nil { 288 err = b.metaCache.PushToCache(b.cacheKey(newPath), data) 289 if err != nil { 290 return err 291 } 292 } 293 err = b.metaCache.RemoveMetadata(b.cacheKey(oldPath)) 294 if err != nil { 295 return err 296 } 297 298 return os.Rename(b.MetadataPath(oldPath), b.MetadataPath(newPath)) 299 } 300 301 // MetadataPath returns the path of the file holding the metadata for the given path 302 func (MessagePackBackend) MetadataPath(path string) string { return path + ".mpk" } 303 304 // LockfilePath returns the path of the lock file 305 func (MessagePackBackend) LockfilePath(path string) string { return path + ".mlock" } 306 307 // Lock locks the metadata for the given path 308 func (b MessagePackBackend) Lock(path string) (UnlockFunc, error) { 309 metaLockPath := b.LockfilePath(path) 310 mlock, err := lockedfile.OpenFile(metaLockPath, os.O_RDWR|os.O_CREATE, 0600) 311 if err != nil { 312 return nil, err 313 } 314 return func() error { 315 err := mlock.Close() 316 if err != nil { 317 return err 318 } 319 return os.Remove(metaLockPath) 320 }, nil 321 } 322 323 func (b MessagePackBackend) cacheKey(path string) string { 324 // rootPath is guaranteed to have no trailing slash 325 // the cache key shouldn't begin with a slash as some stores drop it which can cause 326 // confusion 327 return strings.TrimPrefix(path, b.rootPath+"/") 328 }