summaryrefslogtreecommitdiff
path: root/libpod/lock/file/file_lock.go
blob: 1379e690a2716c1a155f5658ea7f6c3a8799e714 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
package file

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strconv"
	"syscall"

	"github.com/containers/storage"
	"github.com/sirupsen/logrus"
)

// FileLocks is a struct enabling POSIX lock locking in a shared memory
// segment.
type FileLocks struct { //nolint:revive // struct name stutters
	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, fmt.Errorf("directory %s exists: %w", path, syscall.EEXIST)
	}
	if err := os.MkdirAll(path, 0711); err != nil {
		return nil, err
	}

	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, err
	}

	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 fmt.Errorf("locks have already been closed: %w", syscall.EINVAL)
	}
	err := os.RemoveAll(locks.lockPath)
	if err != nil {
		return fmt.Errorf("deleting directory %s: %w", locks.lockPath, err)
	}
	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, fmt.Errorf("locks have already been closed: %w", syscall.EINVAL)
	}

	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, fmt.Errorf("creating lock file: %w", err)
		}
		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 fmt.Errorf("locks have already been closed: %w", syscall.EINVAL)
	}

	f, err := os.OpenFile(locks.getLockPath(lck), os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666)
	if err != nil {
		return fmt.Errorf("error creating lock %d: %w", lck, err)
	}
	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 fmt.Errorf("locks have already been closed: %w", syscall.EINVAL)
	}
	if err := os.Remove(locks.getLockPath(lck)); err != nil {
		return fmt.Errorf("deallocating lock %d: %w", lck, err)
	}
	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 fmt.Errorf("locks have already been closed: %w", syscall.EINVAL)
	}
	files, err := ioutil.ReadDir(locks.lockPath)
	if err != nil {
		return fmt.Errorf("error reading directory %s: %w", locks.lockPath, err)
	}
	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 fmt.Errorf("locks have already been closed: %w", syscall.EINVAL)
	}

	l, err := storage.GetLockfile(locks.getLockPath(lck))
	if err != nil {
		return fmt.Errorf("error acquiring lock: %w", err)
	}

	l.Lock()
	return nil
}

// UnlockFileLock unlocks the given lock.
func (locks *FileLocks) UnlockFileLock(lck uint32) error {
	if !locks.valid {
		return fmt.Errorf("locks have already been closed: %w", syscall.EINVAL)
	}
	l, err := storage.GetLockfile(locks.getLockPath(lck))
	if err != nil {
		return fmt.Errorf("error acquiring lock: %w", err)
	}

	l.Unlock()
	return nil
}