github.com/cs3org/reva/v2@v2.27.7/pkg/storage/utils/decomposedfs/metadata.go (about)

     1  // Copyright 2018-2021 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 decomposedfs
    20  
    21  import (
    22  	"context"
    23  	"fmt"
    24  	"path/filepath"
    25  
    26  	userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
    27  	provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
    28  	"github.com/cs3org/reva/v2/pkg/appctx"
    29  	ctxpkg "github.com/cs3org/reva/v2/pkg/ctx"
    30  	"github.com/cs3org/reva/v2/pkg/errtypes"
    31  	"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/metadata"
    32  	"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/metadata/prefixes"
    33  	"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/node"
    34  	"github.com/cs3org/reva/v2/pkg/storagespace"
    35  	"github.com/cs3org/reva/v2/pkg/utils"
    36  	"github.com/pkg/errors"
    37  )
    38  
    39  // SetArbitraryMetadata sets the metadata on a resource
    40  func (fs *Decomposedfs) SetArbitraryMetadata(ctx context.Context, ref *provider.Reference, md *provider.ArbitraryMetadata) (err error) {
    41  	_, span := tracer.Start(ctx, "SetArbitraryMetadata")
    42  	defer span.End()
    43  	n, err := fs.lu.NodeFromResource(ctx, ref)
    44  	if err != nil {
    45  		return errors.Wrap(err, "Decomposedfs: error resolving ref")
    46  	}
    47  	sublog := appctx.GetLogger(ctx).With().Str("spaceid", n.SpaceID).Str("nodeid", n.ID).Logger()
    48  
    49  	if !n.Exists {
    50  		err = errtypes.NotFound(filepath.Join(n.ParentID, n.Name))
    51  		return err
    52  	}
    53  
    54  	rp, err := fs.p.AssemblePermissions(ctx, n)
    55  	switch {
    56  	case err != nil:
    57  		return err
    58  	case !rp.InitiateFileUpload: // TODO add explicit SetArbitraryMetadata grant to CS3 api, tracked in https://github.com/cs3org/cs3apis/issues/91
    59  		f, _ := storagespace.FormatReference(ref)
    60  		if rp.Stat {
    61  			return errtypes.PermissionDenied(f)
    62  		}
    63  		return errtypes.NotFound(f)
    64  	}
    65  
    66  	// Set space owner in context
    67  	storagespace.ContextSendSpaceOwnerID(ctx, n.SpaceOwnerOrManager(ctx))
    68  
    69  	// check lock
    70  	if err := n.CheckLock(ctx); err != nil {
    71  		return err
    72  	}
    73  
    74  	errs := []error{}
    75  	// TODO should we really continue updating when an error occurs?
    76  	if md.Metadata != nil {
    77  		if val, ok := md.Metadata["mtime"]; ok {
    78  			delete(md.Metadata, "mtime")
    79  			if err := n.SetMtimeString(ctx, val); err != nil {
    80  				errs = append(errs, errors.Wrap(err, "could not set mtime"))
    81  			}
    82  		}
    83  		// TODO(jfd) special handling for atime?
    84  		// TODO(jfd) allow setting birth time (btime)?
    85  		// TODO(jfd) any other metadata that is interesting? fileid?
    86  		// TODO unset when file is updated
    87  		// TODO unset when folder is updated or add timestamp to etag?
    88  		if val, ok := md.Metadata["etag"]; ok {
    89  			delete(md.Metadata, "etag")
    90  			if err := n.SetEtag(ctx, val); err != nil {
    91  				errs = append(errs, errors.Wrap(err, "could not set etag"))
    92  			}
    93  		}
    94  		if val, ok := md.Metadata[node.FavoriteKey]; ok {
    95  			delete(md.Metadata, node.FavoriteKey)
    96  			if u, ok := ctxpkg.ContextGetUser(ctx); ok {
    97  				if uid := u.GetId(); uid != nil {
    98  					if err := n.SetFavorite(ctx, uid, val); err != nil {
    99  						sublog.Error().Err(err).
   100  							Interface("user", u).
   101  							Msg("could not set favorite flag")
   102  						errs = append(errs, errors.Wrap(err, "could not set favorite flag"))
   103  					}
   104  				} else {
   105  					sublog.Error().Interface("user", u).Msg("user has no id")
   106  					errs = append(errs, errors.Wrap(errtypes.UserRequired("userrequired"), "user has no id"))
   107  				}
   108  			} else {
   109  				sublog.Error().Interface("user", u).Msg("error getting user from ctx")
   110  				errs = append(errs, errors.Wrap(errtypes.UserRequired("userrequired"), "error getting user from ctx"))
   111  			}
   112  		}
   113  	}
   114  	for k, v := range md.Metadata {
   115  		attrName := prefixes.MetadataPrefix + k
   116  		if err = n.SetXattrString(ctx, attrName, v); err != nil {
   117  			errs = append(errs, errors.Wrap(err, "Decomposedfs: could not set metadata attribute "+attrName+" to "+k))
   118  		}
   119  	}
   120  
   121  	switch len(errs) {
   122  	case 0:
   123  		return fs.tp.Propagate(ctx, n, 0)
   124  	case 1:
   125  		// TODO Propagate if anything changed
   126  		return errs[0]
   127  	default:
   128  		// TODO Propagate if anything changed
   129  		// TODO how to return multiple errors?
   130  		return errors.New("multiple errors occurred, see log for details")
   131  	}
   132  }
   133  
   134  // UnsetArbitraryMetadata unsets the metadata on the given resource
   135  func (fs *Decomposedfs) UnsetArbitraryMetadata(ctx context.Context, ref *provider.Reference, keys []string) (err error) {
   136  	_, span := tracer.Start(ctx, "UnsetArbitraryMetadata")
   137  	defer span.End()
   138  	n, err := fs.lu.NodeFromResource(ctx, ref)
   139  	if err != nil {
   140  		return errors.Wrap(err, "Decomposedfs: error resolving ref")
   141  	}
   142  	sublog := appctx.GetLogger(ctx).With().Str("spaceid", n.SpaceID).Str("nodeid", n.ID).Logger()
   143  
   144  	if !n.Exists {
   145  		err = errtypes.NotFound(filepath.Join(n.ParentID, n.Name))
   146  		return err
   147  	}
   148  
   149  	rp, err := fs.p.AssemblePermissions(ctx, n)
   150  	switch {
   151  	case err != nil:
   152  		return err
   153  	case !rp.InitiateFileUpload: // TODO use SetArbitraryMetadata grant to CS3 api, tracked in https://github.com/cs3org/cs3apis/issues/91
   154  		f, _ := storagespace.FormatReference(ref)
   155  		if rp.Stat {
   156  			return errtypes.PermissionDenied(f)
   157  		}
   158  		return errtypes.NotFound(f)
   159  	}
   160  
   161  	// Set space owner in context
   162  	storagespace.ContextSendSpaceOwnerID(ctx, n.SpaceOwnerOrManager(ctx))
   163  
   164  	// check lock
   165  	if err := n.CheckLock(ctx); err != nil {
   166  		return err
   167  	}
   168  
   169  	errs := []error{}
   170  	for _, k := range keys {
   171  		switch k {
   172  		case node.FavoriteKey:
   173  			// the favorite flag is specific to the user, so we need to incorporate the userid
   174  			u, ok := ctxpkg.ContextGetUser(ctx)
   175  			if !ok {
   176  				sublog.Error().
   177  					Interface("user", u).
   178  					Msg("error getting user from ctx")
   179  				errs = append(errs, errors.Wrap(errtypes.UserRequired("userrequired"), "error getting user from ctx"))
   180  				continue
   181  			}
   182  			var uid *userpb.UserId
   183  			if uid = u.GetId(); uid == nil || uid.OpaqueId == "" {
   184  				sublog.Error().
   185  					Interface("user", u).
   186  					Msg("user has no id")
   187  				errs = append(errs, errors.Wrap(errtypes.UserRequired("userrequired"), "user has no id"))
   188  				continue
   189  			}
   190  			fa := fmt.Sprintf("%s:%s:%s@%s", prefixes.FavPrefix, utils.UserTypeToString(uid.GetType()), uid.GetOpaqueId(), uid.GetIdp())
   191  			if err := n.RemoveXattr(ctx, fa, true); err != nil {
   192  				if metadata.IsAttrUnset(err) {
   193  					continue // already gone, ignore
   194  				}
   195  				sublog.Error().Err(err).
   196  					Interface("user", u).
   197  					Str("key", fa).
   198  					Msg("could not unset favorite flag")
   199  				errs = append(errs, errors.Wrap(err, "could not unset favorite flag"))
   200  			}
   201  		default:
   202  			if err = n.RemoveXattr(ctx, prefixes.MetadataPrefix+k, true); err != nil {
   203  				if metadata.IsAttrUnset(err) {
   204  					continue // already gone, ignore
   205  				}
   206  				sublog.Error().Err(err).
   207  					Str("key", k).
   208  					Msg("could not unset metadata")
   209  				errs = append(errs, errors.Wrap(err, "could not unset metadata"))
   210  			}
   211  		}
   212  	}
   213  	switch len(errs) {
   214  	case 0:
   215  		return fs.tp.Propagate(ctx, n, 0)
   216  	case 1:
   217  		// TODO Propagate if anything changed
   218  		return errs[0]
   219  	default:
   220  		// TODO Propagate if anything changed
   221  		// TODO how to return multiple errors?
   222  		return errors.New("multiple errors occurred, see log for details")
   223  	}
   224  }