github.com/cs3org/reva/v2@v2.27.7/pkg/storage/utils/decomposedfs/node/locks.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 node 20 21 import ( 22 "context" 23 "encoding/json" 24 "io/fs" 25 "os" 26 "path/filepath" 27 "time" 28 29 provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" 30 types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" 31 "github.com/cs3org/reva/v2/pkg/appctx" 32 ctxpkg "github.com/cs3org/reva/v2/pkg/ctx" 33 "github.com/cs3org/reva/v2/pkg/errtypes" 34 "github.com/cs3org/reva/v2/pkg/storage/utils/filelocks" 35 "github.com/cs3org/reva/v2/pkg/utils" 36 "github.com/pkg/errors" 37 ) 38 39 // SetLock sets a lock on the node 40 func (n *Node) SetLock(ctx context.Context, lock *provider.Lock) error { 41 ctx, span := tracer.Start(ctx, "SetLock") 42 defer span.End() 43 lockFilePath := n.LockFilePath() 44 45 // ensure parent path exists 46 if err := os.MkdirAll(filepath.Dir(lockFilePath), 0700); err != nil { 47 return errors.Wrap(err, "Decomposedfs: error creating parent folder for lock") 48 } 49 50 // get file lock, so that nobody can create the lock in the meantime 51 fileLock, err := filelocks.AcquireWriteLock(n.InternalPath()) 52 if err != nil { 53 return err 54 } 55 56 defer func() { 57 rerr := filelocks.ReleaseLock(fileLock) 58 59 // if err is non nil we do not overwrite that 60 if err == nil { 61 err = rerr 62 } 63 }() 64 65 // check if already locked 66 l, err := n.ReadLock(ctx, true) // we already have a write file lock, so ReadLock() would fail to acquire a read file lock -> skip it 67 switch err.(type) { 68 case errtypes.NotFound: 69 // file not locked, continue 70 case nil: 71 if l != nil { 72 return errtypes.PreconditionFailed("already locked") 73 } 74 default: 75 return errors.Wrap(err, "Decomposedfs: could check if file already is locked") 76 } 77 78 // O_EXCL to make open fail when the file already exists 79 f, err := os.OpenFile(lockFilePath, os.O_EXCL|os.O_CREATE|os.O_WRONLY, 0600) 80 if err != nil { 81 return errors.Wrap(err, "Decomposedfs: could not create lock file") 82 } 83 defer f.Close() 84 85 if err := json.NewEncoder(f).Encode(lock); err != nil { 86 return errors.Wrap(err, "Decomposedfs: could not write lock file") 87 } 88 89 return err 90 } 91 92 // ReadLock reads the lock id for a node 93 func (n Node) ReadLock(ctx context.Context, skipFileLock bool) (*provider.Lock, error) { 94 ctx, span := tracer.Start(ctx, "ReadLock") 95 defer span.End() 96 97 // ensure parent path exists 98 _, subspan := tracer.Start(ctx, "os.MkdirAll") 99 err := os.MkdirAll(filepath.Dir(n.InternalPath()), 0700) 100 subspan.End() 101 if err != nil { 102 return nil, errors.Wrap(err, "Decomposedfs: error creating parent folder for lock") 103 } 104 105 // the caller of ReadLock already may hold a file lock 106 if !skipFileLock { 107 _, subspan := tracer.Start(ctx, "filelocks.AcquireReadLock") 108 fileLock, err := filelocks.AcquireReadLock(n.InternalPath()) 109 subspan.End() 110 111 if err != nil { 112 return nil, err 113 } 114 115 defer func() { 116 _, subspan := tracer.Start(ctx, "filelocks.ReleaseLock") 117 rerr := filelocks.ReleaseLock(fileLock) 118 subspan.End() 119 120 // if err is non nil we do not overwrite that 121 if err == nil { 122 err = rerr 123 } 124 }() 125 } 126 127 _, subspan = tracer.Start(ctx, "os.Open") 128 f, err := os.Open(n.LockFilePath()) 129 subspan.End() 130 131 if err != nil { 132 if errors.Is(err, fs.ErrNotExist) { 133 return nil, errtypes.NotFound("no lock found") 134 } 135 return nil, errors.Wrap(err, "Decomposedfs: could not open lock file") 136 } 137 defer f.Close() 138 139 lock := &provider.Lock{} 140 if err := json.NewDecoder(f).Decode(lock); err != nil { 141 appctx.GetLogger(ctx).Error().Err(err).Msg("Decomposedfs: could not decode lock file, ignoring") 142 return nil, errors.Wrap(err, "Decomposedfs: could not read lock file") 143 } 144 145 // lock already expired 146 if lock.Expiration != nil && time.Now().After(time.Unix(int64(lock.Expiration.Seconds), int64(lock.Expiration.Nanos))) { 147 148 _, subspan = tracer.Start(ctx, "os.Remove") 149 err = os.Remove(f.Name()) 150 subspan.End() 151 if err != nil { 152 return nil, errors.Wrap(err, "Decomposedfs: could not remove expired lock file") 153 } 154 // we successfully deleted the expired lock 155 return nil, errtypes.NotFound("no lock found") 156 } 157 158 return lock, nil 159 } 160 161 // RefreshLock refreshes the node's lock 162 func (n *Node) RefreshLock(ctx context.Context, lock *provider.Lock, existingLockID string) error { 163 ctx, span := tracer.Start(ctx, "RefreshLock") 164 defer span.End() 165 166 // ensure parent path exists 167 if err := os.MkdirAll(filepath.Dir(n.InternalPath()), 0700); err != nil { 168 return errors.Wrap(err, "Decomposedfs: error creating parent folder for lock") 169 } 170 fileLock, err := filelocks.AcquireWriteLock(n.InternalPath()) 171 172 if err != nil { 173 return err 174 } 175 176 defer func() { 177 rerr := filelocks.ReleaseLock(fileLock) 178 179 // if err is non nil we do not overwrite that 180 if err == nil { 181 err = rerr 182 } 183 }() 184 185 f, err := os.OpenFile(n.LockFilePath(), os.O_RDWR, os.ModeExclusive) 186 switch { 187 case errors.Is(err, fs.ErrNotExist): 188 return errtypes.PreconditionFailed("lock does not exist") 189 case err != nil: 190 return errors.Wrap(err, "Decomposedfs: could not open lock file") 191 } 192 defer f.Close() 193 194 readLock := &provider.Lock{} 195 if err := json.NewDecoder(f).Decode(readLock); err != nil { 196 return errors.Wrap(err, "Decomposedfs: could not read lock") 197 } 198 199 // check refresh lockID match 200 if existingLockID == "" && readLock.LockId != lock.LockId { 201 return errtypes.Aborted("mismatching lock ID") 202 } 203 204 // check if UnlockAndRelock sends the correct lockID 205 if existingLockID != "" && readLock.LockId != existingLockID { 206 return errtypes.Aborted("mismatching existing lock ID") 207 } 208 209 if ok, err := isLockModificationAllowed(ctx, readLock, lock); !ok { 210 return err 211 } 212 213 // Rewind to the beginning of the file before writing a refreshed lock 214 _, err = f.Seek(0, 0) 215 if err != nil { 216 return errors.Wrap(err, "could not seek to the beginning of the lock file") 217 } 218 if err := json.NewEncoder(f).Encode(lock); err != nil { 219 return errors.Wrap(err, "Decomposedfs: could not write lock file") 220 } 221 222 return err 223 } 224 225 // Unlock unlocks the node 226 func (n *Node) Unlock(ctx context.Context, lock *provider.Lock) error { 227 ctx, span := tracer.Start(ctx, "Unlock") 228 defer span.End() 229 230 // ensure parent path exists 231 if err := os.MkdirAll(filepath.Dir(n.InternalPath()), 0700); err != nil { 232 return errors.Wrap(err, "Decomposedfs: error creating parent folder for lock") 233 } 234 fileLock, err := filelocks.AcquireWriteLock(n.InternalPath()) 235 236 if err != nil { 237 return err 238 } 239 240 defer func() { 241 rerr := filelocks.ReleaseLock(fileLock) 242 243 // if err is non nil we do not overwrite that 244 if err == nil { 245 err = rerr 246 } 247 }() 248 249 f, err := os.OpenFile(n.LockFilePath(), os.O_RDONLY, os.ModeExclusive) 250 switch { 251 case errors.Is(err, fs.ErrNotExist): 252 return errtypes.Aborted("lock does not exist") 253 case err != nil: 254 return errors.Wrap(err, "Decomposedfs: could not open lock file") 255 } 256 defer f.Close() 257 258 oldLock := &provider.Lock{} 259 if err := json.NewDecoder(f).Decode(oldLock); err != nil { 260 return errors.Wrap(err, "Decomposedfs: could not read lock") 261 } 262 263 // check lock 264 if lock == nil || (oldLock.LockId != lock.LockId) { 265 return errtypes.Locked(oldLock.LockId) 266 } 267 268 if ok, err := isLockModificationAllowed(ctx, oldLock, lock); !ok { 269 return err 270 } 271 272 if err = os.Remove(f.Name()); err != nil { 273 return errors.Wrap(err, "Decomposedfs: could not remove lock file") 274 } 275 return err 276 } 277 278 // CheckLock compares the context lock with the node lock 279 func (n *Node) CheckLock(ctx context.Context) error { 280 ctx, span := tracer.Start(ctx, "CheckLock") 281 defer span.End() 282 contextLock, _ := ctxpkg.ContextGetLockID(ctx) 283 diskLock, _ := n.ReadLock(ctx, false) 284 if diskLock != nil { 285 switch contextLock { 286 case "": 287 return errtypes.Locked(diskLock.LockId) // no lockid in request 288 case diskLock.LockId: 289 return nil // ok 290 default: 291 return errtypes.Aborted("mismatching lock") 292 } 293 } 294 if contextLock != "" { 295 return errtypes.Aborted("not locked") // no lock on disk. why is there a lockid in the context 296 } 297 return nil // ok 298 } 299 300 func readLocksIntoOpaque(ctx context.Context, n *Node, ri *provider.ResourceInfo) error { 301 lock, err := n.ReadLock(ctx, false) 302 if err != nil { 303 appctx.GetLogger(ctx).Error().Err(err).Msg("Decomposedfs: could not read lock") 304 return err 305 } 306 307 // reencode to ensure valid json 308 var b []byte 309 if b, err = json.Marshal(lock); err != nil { 310 appctx.GetLogger(ctx).Error().Err(err).Msg("Decomposedfs: could not marshal locks") 311 } 312 if ri.Opaque == nil { 313 ri.Opaque = &types.Opaque{ 314 Map: map[string]*types.OpaqueEntry{}, 315 } 316 } 317 ri.Opaque.Map["lock"] = &types.OpaqueEntry{ 318 Decoder: "json", 319 Value: b, 320 } 321 ri.Lock = lock 322 return err 323 } 324 325 func (n *Node) hasLocks(ctx context.Context) bool { 326 _, err := os.Stat(n.LockFilePath()) // FIXME better error checking 327 return err == nil 328 } 329 330 func isLockModificationAllowed(ctx context.Context, oldLock *provider.Lock, newLock *provider.Lock) (bool, error) { 331 if oldLock.Type == provider.LockType_LOCK_TYPE_SHARED { 332 return true, nil 333 } 334 335 appNameEquals := oldLock.AppName == newLock.AppName 336 if !appNameEquals { 337 return false, errtypes.PermissionDenied("app names of the locks are mismatching") 338 } 339 340 var lockUserEquals, contextUserEquals bool 341 if oldLock.User == nil && newLock.GetUser() == nil { 342 // no user lock set 343 lockUserEquals = true 344 contextUserEquals = true 345 } else { 346 lockUserEquals = utils.UserIDEqual(oldLock.User, newLock.GetUser()) 347 if !lockUserEquals { 348 return false, errtypes.PermissionDenied("users of the locks are mismatching") 349 } 350 351 u := ctxpkg.ContextMustGetUser(ctx) 352 contextUserEquals = utils.UserIDEqual(oldLock.User, u.Id) 353 if !contextUserEquals { 354 return false, errtypes.PermissionDenied("lock holder and current user are mismatching") 355 } 356 } 357 358 return appNameEquals && lockUserEquals && contextUserEquals, nil 359 360 }