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  }