lock: new lock type "file"

it is a wrapper around containers/storage file locking.

Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
This commit is contained in:
Giuseppe Scrivano
2019-06-27 12:56:29 +02:00
parent 82164a2e9e
commit 827ac0859f
5 changed files with 377 additions and 0 deletions

View File

@ -27,6 +27,9 @@ libpod to manage containers.
**cgroup_manager**=""
Specify the CGroup Manager to use; valid values are "systemd" and "cgroupfs"
**lock_type**=""
Specify the locking mechanism to use; valid values are "shm" and "file". Change the default only if you are sure of what you are doing, in general "file" is useful only on platforms where cgo is not available for using the faster "shm" lock type. You may need to run "podman system renumber" after you change the lock type.
**init_path**=""
Path to the container-init binary, which forwards signals and reaps processes within containers. Note that the container-init binary will only be used when the `--init` for podman-create and podman-run is set.

View File

@ -0,0 +1,175 @@
package file
import (
"io/ioutil"
"os"
"path/filepath"
"strconv"
"syscall"
"github.com/containers/storage"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
// FileLocks is a struct enabling POSIX lock locking in a shared memory
// segment.
type FileLocks struct { // nolint
lockPath string
valid bool
}
// CreateFileLock sets up a directory containing the various lock files.
func CreateFileLock(path string) (*FileLocks, error) {
_, err := os.Stat(path)
if err == nil {
return nil, errors.Wrapf(syscall.EEXIST, "directory %s exists", path)
}
if err := os.MkdirAll(path, 0711); err != nil {
return nil, errors.Wrapf(err, "cannot create %s", path)
}
locks := new(FileLocks)
locks.lockPath = path
locks.valid = true
return locks, nil
}
// OpenFileLock opens an existing directory with the lock files.
func OpenFileLock(path string) (*FileLocks, error) {
_, err := os.Stat(path)
if err != nil {
return nil, errors.Wrapf(err, "accessing directory %s", path)
}
locks := new(FileLocks)
locks.lockPath = path
locks.valid = true
return locks, nil
}
// Close closes an existing shared-memory segment.
// The segment will be rendered unusable after closing.
// WARNING: If you Close() while there are still locks locked, these locks may
// fail to release, causing a program freeze.
// Close() is only intended to be used while testing the locks.
func (locks *FileLocks) Close() error {
if !locks.valid {
return errors.Wrapf(syscall.EINVAL, "locks have already been closed")
}
err := os.RemoveAll(locks.lockPath)
if err != nil {
return errors.Wrapf(err, "deleting directory %s", locks.lockPath)
}
return nil
}
func (locks *FileLocks) getLockPath(lck uint32) string {
return filepath.Join(locks.lockPath, strconv.FormatInt(int64(lck), 10))
}
// AllocateLock allocates a lock and returns the index of the lock that was allocated.
func (locks *FileLocks) AllocateLock() (uint32, error) {
if !locks.valid {
return 0, errors.Wrapf(syscall.EINVAL, "locks have already been closed")
}
id := uint32(0)
for ; ; id++ {
path := locks.getLockPath(id)
f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666)
if err != nil {
if os.IsExist(err) {
continue
}
return 0, errors.Wrapf(err, "creating lock file")
}
f.Close()
break
}
return id, nil
}
// AllocateGivenLock allocates the given lock from the shared-memory
// segment for use by a container or pod.
// If the lock is already in use or the index is invalid an error will be
// returned.
func (locks *FileLocks) AllocateGivenLock(lck uint32) error {
if !locks.valid {
return errors.Wrapf(syscall.EINVAL, "locks have already been closed")
}
f, err := os.OpenFile(locks.getLockPath(lck), os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666)
if err != nil {
return errors.Wrapf(err, "error creating lock %d", lck)
}
f.Close()
return nil
}
// DeallocateLock frees a lock in a shared-memory segment so it can be
// reallocated to another container or pod.
// The given lock must be already allocated, or an error will be returned.
func (locks *FileLocks) DeallocateLock(lck uint32) error {
if !locks.valid {
return errors.Wrapf(syscall.EINVAL, "locks have already been closed")
}
if err := os.Remove(locks.getLockPath(lck)); err != nil {
return errors.Wrapf(err, "deallocating lock %d", lck)
}
return nil
}
// DeallocateAllLocks frees all locks so they can be reallocated to
// other containers and pods.
func (locks *FileLocks) DeallocateAllLocks() error {
if !locks.valid {
return errors.Wrapf(syscall.EINVAL, "locks have already been closed")
}
files, err := ioutil.ReadDir(locks.lockPath)
if err != nil {
return errors.Wrapf(err, "error reading directory %s", locks.lockPath)
}
var lastErr error
for _, f := range files {
p := filepath.Join(locks.lockPath, f.Name())
err := os.Remove(p)
if err != nil {
lastErr = err
logrus.Errorf("deallocating lock %s", p)
}
}
return lastErr
}
// LockFileLock locks the given lock.
func (locks *FileLocks) LockFileLock(lck uint32) error {
if !locks.valid {
return errors.Wrapf(syscall.EINVAL, "locks have already been closed")
}
l, err := storage.GetLockfile(locks.getLockPath(lck))
if err != nil {
return errors.Wrapf(err, "error acquiring lock")
}
l.Lock()
return nil
}
// UnlockFileLock unlocks the given lock.
func (locks *FileLocks) UnlockFileLock(lck uint32) error {
if !locks.valid {
return errors.Wrapf(syscall.EINVAL, "locks have already been closed")
}
l, err := storage.GetLockfile(locks.getLockPath(lck))
if err != nil {
return errors.Wrapf(err, "error acquiring lock")
}
l.Unlock()
return nil
}

View File

@ -0,0 +1,74 @@
package file
import (
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
)
// Test that creating and destroying locks work
func TestCreateAndDeallocate(t *testing.T) {
d, err := ioutil.TempDir("", "filelock")
assert.NoError(t, err)
defer os.RemoveAll(d)
l, err := OpenFileLock(filepath.Join(d, "locks"))
assert.Error(t, err)
l, err = CreateFileLock(filepath.Join(d, "locks"))
assert.NoError(t, err)
lock, err := l.AllocateLock()
assert.NoError(t, err)
err = l.AllocateGivenLock(lock)
assert.Error(t, err)
err = l.DeallocateLock(lock)
assert.NoError(t, err)
err = l.AllocateGivenLock(lock)
assert.NoError(t, err)
err = l.DeallocateAllLocks()
assert.NoError(t, err)
err = l.AllocateGivenLock(lock)
assert.NoError(t, err)
err = l.DeallocateAllLocks()
assert.NoError(t, err)
}
// Test that creating and destroying locks work
func TestLockAndUnlock(t *testing.T) {
d, err := ioutil.TempDir("", "filelock")
assert.NoError(t, err)
defer os.RemoveAll(d)
l, err := CreateFileLock(filepath.Join(d, "locks"))
assert.NoError(t, err)
lock, err := l.AllocateLock()
assert.NoError(t, err)
err = l.LockFileLock(lock)
assert.NoError(t, err)
lslocks, err := exec.LookPath("lslocks")
if err == nil {
lockPath := l.getLockPath(lock)
out, err := exec.Command(lslocks, "--json", "-p", fmt.Sprintf("%d", os.Getpid())).CombinedOutput()
assert.NoError(t, err)
assert.Contains(t, string(out), lockPath)
}
err = l.UnlockFileLock(lock)
assert.NoError(t, err)
}

View File

@ -0,0 +1,110 @@
package lock
import (
"github.com/containers/libpod/libpod/lock/file"
)
// FileLockManager manages shared memory locks.
type FileLockManager struct {
locks *file.FileLocks
}
// NewFileLockManager makes a new FileLockManager at the specified directory.
func NewFileLockManager(lockPath string) (Manager, error) {
locks, err := file.CreateFileLock(lockPath)
if err != nil {
return nil, err
}
manager := new(FileLockManager)
manager.locks = locks
return manager, nil
}
// OpenFileLockManager opens an existing FileLockManager at the specified directory.
func OpenFileLockManager(path string) (Manager, error) {
locks, err := file.OpenFileLock(path)
if err != nil {
return nil, err
}
manager := new(FileLockManager)
manager.locks = locks
return manager, nil
}
// AllocateLock allocates a new lock from the manager.
func (m *FileLockManager) AllocateLock() (Locker, error) {
semIndex, err := m.locks.AllocateLock()
if err != nil {
return nil, err
}
lock := new(FileLock)
lock.lockID = semIndex
lock.manager = m
return lock, nil
}
// AllocateAndRetrieveLock allocates the lock with the given ID and returns it.
// If the lock is already allocated, error.
func (m *FileLockManager) AllocateAndRetrieveLock(id uint32) (Locker, error) {
lock := new(FileLock)
lock.lockID = id
lock.manager = m
if err := m.locks.AllocateGivenLock(id); err != nil {
return nil, err
}
return lock, nil
}
// RetrieveLock retrieves a lock from the manager given its ID.
func (m *FileLockManager) RetrieveLock(id uint32) (Locker, error) {
lock := new(FileLock)
lock.lockID = id
lock.manager = m
return lock, nil
}
// FreeAllLocks frees all locks in the manager.
// This function is DANGEROUS. Please read the full comment in locks.go before
// trying to use it.
func (m *FileLockManager) FreeAllLocks() error {
return m.locks.DeallocateAllLocks()
}
// FileLock is an individual shared memory lock.
type FileLock struct {
lockID uint32
manager *FileLockManager
}
// ID returns the ID of the lock.
func (l *FileLock) ID() uint32 {
return l.lockID
}
// Lock acquires the lock.
func (l *FileLock) Lock() {
if err := l.manager.locks.LockFileLock(l.lockID); err != nil {
panic(err.Error())
}
}
// Unlock releases the lock.
func (l *FileLock) Unlock() {
if err := l.manager.locks.UnlockFileLock(l.lockID); err != nil {
panic(err.Error())
}
}
// Free releases the lock, allowing it to be reused.
func (l *FileLock) Free() error {
return l.manager.locks.DeallocateLock(l.lockID)
}

View File

@ -318,6 +318,7 @@ func defaultRuntimeConfig() (RuntimeConfig, error) {
NumLocks: 2048,
EventsLogger: events.DefaultEventerType.String(),
DetachKeys: DefaultDetachKeys,
LockType: "shm",
}, nil
}
@ -664,6 +665,20 @@ func getLockManager(runtime *Runtime) (lock.Manager, error) {
var manager lock.Manager
switch runtime.config.LockType {
case "file":
lockPath := filepath.Join(runtime.config.TmpDir, "locks")
manager, err = lock.OpenFileLockManager(lockPath)
if err != nil {
if os.IsNotExist(errors.Cause(err)) {
manager, err = lock.NewFileLockManager(lockPath)
if err != nil {
return nil, errors.Wrapf(err, "failed to get new file lock manager")
}
} else {
return nil, err
}
}
case "", "shm":
lockPath := DefaultSHMLockPath
if rootless.IsRootless() {