diff --git a/cephfs/file.go b/cephfs/file.go index 60f4bfa..bd3e2f9 100644 --- a/cephfs/file.go +++ b/cephfs/file.go @@ -255,3 +255,41 @@ func (f *File) Fallocate(mode FallocFlags, offset, length int64) error { ret := C.ceph_fallocate(f.mount.mount, f.fd, C.int(mode), C.int64_t(offset), C.int64_t(length)) return getError(ret) } + +// LockOp determines operations/type of locks which can be applied on a file. +type LockOp int + +const ( + // LockSH places a shared lock. + // More than one process may hold a shared lock for a given file at a given time. + LockSH = LockOp(C.LOCK_SH) + // LockEX places an exclusive lock. + // Only one process may hold an exclusive lock for a given file at a given time. + LockEX = LockOp(C.LOCK_EX) + // LockUN removes and existing lock held by this process. + LockUN = LockOp(C.LOCK_UN) + // LockNB can be ORed with any of the above to make a nonblocking call. + LockNB = LockOp(C.LOCK_NB) +) + +// Flock applies or removes an advisory lock on an open file. +// Param owner is the user-supplied identifier for the owner of the +// lock, must be an arbitrary integer. +// +// Implements: +// int ceph_flock(struct ceph_mount_info *cmount, int fd, int operation, uint64_t owner); +func (f *File) Flock(operation LockOp, owner uint64) error { + if err := f.validate(); err != nil { + return err + } + + // validate the operation values before passing it on. + switch operation &^ LockNB { + case LockSH, LockEX, LockUN: + default: + return errInvalid + } + + ret := C.ceph_flock(f.mount.mount, f.fd, C.int(operation), C.uint64_t(owner)) + return getError(ret) +} diff --git a/cephfs/file_test.go b/cephfs/file_test.go index 836d7df..d930b68 100644 --- a/cephfs/file_test.go +++ b/cephfs/file_test.go @@ -615,3 +615,99 @@ func TestFallocate(t *testing.T) { assert.Error(t, err) }) } + +func TestFlock(t *testing.T) { + mount := fsConnect(t) + defer fsDisconnect(t, mount) + + t.Run("validate", func(t *testing.T) { + f := &File{} + err := f.Flock(LockSH, 1010) + assert.Error(t, err) + }) + + t.Run("validateOperation", func(t *testing.T) { + fname := "Flockfile.txt" + f, err := mount.Open(fname, os.O_RDWR|os.O_CREATE, 0666) + assert.NoError(t, err) + assert.NotNil(t, f) + defer func() { + assert.NoError(t, f.Close()) + assert.NoError(t, mount.Unlink(fname)) + }() + err = f.Flock(LockSH|LockEX, 1010) + assert.Error(t, err) + }) + + t.Run("basicLocking", func(t *testing.T) { + const ( + anna = 42 + bob = 43 + chris = 44 + ) + fname1 := "Flockfile1.txt" + f1, err := mount.Open(fname1, os.O_RDWR|os.O_CREATE, 0666) + assert.NoError(t, err) + assert.NotNil(t, f1) + defer func() { + assert.NoError(t, f1.Close()) + assert.NoError(t, mount.Unlink(fname1)) + }() + // Lock exclusively twice. + t.Run("exclusiveTwiceBlock", func(t *testing.T) { + err := f1.Flock(LockEX, anna) + assert.NoError(t, err) + defer func() { + assert.NoError(t, f1.Flock(LockUN, anna)) + }() + err = f1.Flock(LockEX|LockNB, bob) + assert.Error(t, err) + }) + t.Run("exclusiveTwiceNonBlock", func(t *testing.T) { + err := f1.Flock(LockEX|LockNB, anna) + assert.NoError(t, err) + defer func() { + assert.NoError(t, f1.Flock(LockUN, anna)) + }() + err = f1.Flock(LockEX|LockNB, bob) + assert.Error(t, err) + }) + + // Lock shared. + t.Run("sharedLock", func(t *testing.T) { + err := f1.Flock(LockSH, anna) + assert.NoError(t, err) + err = f1.Flock(LockSH, bob) + assert.NoError(t, err) + defer func() { + assert.NoError(t, f1.Flock(LockUN, anna)) + assert.NoError(t, f1.Flock(LockUN, bob)) + }() + // Now try to take exclusive lock. + err = f1.Flock(LockEX|LockNB, chris) + assert.Error(t, err) + }) + + // Lock shared with upgrade to exclusive. + t.Run("sharedLockUpExclusive", func(t *testing.T) { + err := f1.Flock(LockSH, bob) + assert.NoError(t, err) + defer func() { + assert.NoError(t, f1.Flock(LockUN, bob)) + }() + err = f1.Flock(LockEX, bob) + assert.NoError(t, err) + }) + + // Lock exclusive with downgrade to shared. + t.Run("exclusiveLockDownShared", func(t *testing.T) { + err := f1.Flock(LockEX, bob) + assert.NoError(t, err) + defer func() { + assert.NoError(t, f1.Flock(LockUN, bob)) + }() + err = f1.Flock(LockSH, bob) + assert.NoError(t, err) + }) + }) +}