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  }