diff --git a/virtcontainers/store/backend.go b/virtcontainers/store/backend.go index e183e96054..99a56317c1 100644 --- a/virtcontainers/store/backend.go +++ b/virtcontainers/store/backend.go @@ -53,4 +53,6 @@ type backend interface { // The caller gets an item URL back and handles it directly, // outside of the top level Store API. raw(id string) (string, error) + lock(item Item, exclusive bool) (string, error) + unlock(item Item, token string) error } diff --git a/virtcontainers/store/filesystem_backend.go b/virtcontainers/store/filesystem_backend.go index 5c6206d318..e57f8b7ed3 100644 --- a/virtcontainers/store/filesystem_backend.go +++ b/virtcontainers/store/filesystem_backend.go @@ -12,7 +12,9 @@ import ( "io/ioutil" "os" "path/filepath" + "syscall" + "github.com/kata-containers/runtime/virtcontainers/pkg/uuid" opentracing "github.com/opentracing/opentracing-go" "github.com/sirupsen/logrus" ) @@ -103,6 +105,8 @@ type filesystem struct { path string rawPath string + + lockTokens map[string]*os.File } // Logger returns a logrus logger appropriate for logging Store filesystem messages @@ -174,6 +178,7 @@ func (f *filesystem) new(ctx context.Context, path string, host string) error { f.ctx = ctx f.path = path f.rawPath = filepath.Join(f.path, "raw") + f.lockTokens = make(map[string]*os.File) f.logger().Debugf("New filesystem store backend for %s", path) @@ -259,3 +264,48 @@ func (f *filesystem) raw(id string) (string, error) { return filesystemScheme + "://" + file.Name(), nil } + +func (f *filesystem) lock(item Item, exclusive bool) (string, error) { + itemPath, err := f.itemToPath(item) + if err != nil { + return "", err + } + + itemFile, err := os.Open(itemPath) + if err != nil { + return "", err + } + + var lockType int + if exclusive { + lockType = syscall.LOCK_EX + } else { + lockType = syscall.LOCK_SH + } + + if err := syscall.Flock(int(itemFile.Fd()), lockType); err != nil { + itemFile.Close() + return "", err + } + + token := uuid.Generate().String() + f.lockTokens[token] = itemFile + + return token, nil +} + +func (f *filesystem) unlock(item Item, token string) error { + itemFile := f.lockTokens[token] + if itemFile == nil { + return fmt.Errorf("No lock for token %s", token) + } + + if err := syscall.Flock(int(itemFile.Fd()), syscall.LOCK_UN); err != nil { + return err + } + + itemFile.Close() + delete(f.lockTokens, token) + + return nil +} diff --git a/virtcontainers/store/filesystem_backend_test.go b/virtcontainers/store/filesystem_backend_test.go index 4d62a043c0..28589ae57f 100644 --- a/virtcontainers/store/filesystem_backend_test.go +++ b/virtcontainers/store/filesystem_backend_test.go @@ -99,3 +99,51 @@ func TestStoreFilesystemRaw(t *testing.T) { assert.Nil(t, err) assert.Equal(t, path, filesystemScheme+"://"+filepath.Join(rootPath, "raw", "roah")) } + +func TestStoreFilesystemLockShared(t *testing.T) { + f := filesystem{} + + err := f.new(context.Background(), rootPath, "") + defer f.delete() + assert.Nil(t, err) + + // Take 2 shared locks + token1, err := f.lock(Lock, false) + assert.Nil(t, err) + + token2, err := f.lock(Lock, false) + assert.Nil(t, err) + + err = f.unlock(Lock, token1) + assert.Nil(t, err) + + err = f.unlock(Lock, token2) + assert.Nil(t, err) + + err = f.unlock(Lock, token2) + assert.NotNil(t, err) +} + +func TestStoreFilesystemLockExclusive(t *testing.T) { + f := filesystem{} + + err := f.new(context.Background(), rootPath, "") + defer f.delete() + assert.Nil(t, err) + + // Take 1 exclusive lock + token, err := f.lock(Lock, true) + assert.Nil(t, err) + + err = f.unlock(Lock, token) + assert.Nil(t, err) + + token, err = f.lock(Lock, true) + assert.Nil(t, err) + + err = f.unlock(Lock, token) + assert.Nil(t, err) + + err = f.unlock(Lock, token) + assert.NotNil(t, err) +} diff --git a/virtcontainers/store/manager.go b/virtcontainers/store/manager.go index d5e96b2e48..fa18ab586d 100644 --- a/virtcontainers/store/manager.go +++ b/virtcontainers/store/manager.go @@ -261,3 +261,13 @@ func (s *Store) Raw(id string) (string, error) { return s.backend.raw(id) } + +// ItemLock takes a lock on an item. +func (s *Store) ItemLock(item Item, exclusive bool) (string, error) { + return s.backend.lock(item, exclusive) +} + +// ItemUnlock unlocks an item. +func (s *Store) ItemUnlock(item Item, token string) error { + return s.backend.unlock(item, token) +} diff --git a/virtcontainers/store/vc.go b/virtcontainers/store/vc.go index d3b0835624..b51cf725aa 100644 --- a/virtcontainers/store/vc.go +++ b/virtcontainers/store/vc.go @@ -202,6 +202,21 @@ func (s *VCStore) Raw(id string) (string, error) { return s.state.Raw(id) } +// Lock takes an exclusive lock on the virtcontainers state Lock item. +func (s *VCStore) Lock() (string, error) { + return s.state.ItemLock(Lock, true) +} + +// RLock takes a shared lock on the virtcontainers state Lock item. +func (s *VCStore) RLock() (string, error) { + return s.state.ItemLock(Lock, false) +} + +// Unlock unlocks the virtcontainers state Lock item. +func (s *VCStore) Unlock(token string) error { + return s.state.ItemUnlock(Lock, token) +} + // Utilities for virtcontainers // SandboxConfigurationRoot returns a virtcontainers sandbox configuration root URL.