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  }