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 }