forked from project-stacker/stacker
-
Notifications
You must be signed in to change notification settings - Fork 0
/
lock.go
127 lines (103 loc) · 2.95 KB
/
lock.go
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
package stacker
import (
"bytes"
"io/ioutil"
"os"
"path"
"strconv"
"strings"
"syscall"
"github.com/pkg/errors"
"github.com/project-stacker/stacker/types"
)
func findLock(st *syscall.Stat_t) error {
content, err := ioutil.ReadFile("/proc/locks")
if err != nil {
return errors.Wrapf(err, "failed to read locks file")
}
for _, line := range strings.Split(string(content), "\n") {
if len(line) == 0 {
continue
}
fields := strings.Fields(line)
if len(fields) < 8 {
return errors.Errorf("invalid lock file entry %s", line)
}
entries := strings.Split(fields[5], ":")
if len(entries) != 3 {
return errors.Errorf("invalid lock file field %s", fields[5])
}
/*
* XXX: the kernel prints "fd:01:$ino" for some (all?) locks,
* even though the man page we should be able to use fields 0
* and 1 as major and minor device types. Let's just ignore
* these.
*/
ino, err := strconv.ParseUint(entries[2], 10, 64)
if err != nil {
return errors.Wrapf(err, "invalid ino %s", entries[2])
}
if st.Ino != ino {
continue
}
pid := fields[4]
content, err := ioutil.ReadFile(path.Join("/proc", pid, "cmdline"))
if err != nil {
return errors.Errorf("lock owned by pid %s", pid)
}
content = bytes.Replace(content, []byte{0}, []byte{' '}, -1)
return errors.Errorf("lock owned by pid %s (%s)", pid, string(content))
}
return errors.Errorf("couldn't find who owns the lock")
}
func acquireLock(p string) (*os.File, error) {
lockfile, err := os.Create(p)
if err != nil {
return nil, errors.Wrapf(err, "couldn't create lockfile %s", p)
}
lockMode := syscall.LOCK_EX
lockErr := syscall.Flock(int(lockfile.Fd()), lockMode|syscall.LOCK_NB)
if lockErr == nil {
return lockfile, nil
}
fi, err := lockfile.Stat()
lockfile.Close()
if err != nil {
return nil, errors.Wrapf(err, "couldn't lock or stat lockfile %s", p)
}
owner := findLock(fi.Sys().(*syscall.Stat_t))
return nil, errors.Wrapf(lockErr, "couldn't acquire lock on %s: %v", p, owner)
}
const lockPath = ".lock"
type StackerLocks struct {
stackerDir, rootsDir *os.File
}
func (ls *StackerLocks) Unlock() {
// TODO: it would be good to lock the OCI dir here, because I can
// imagine two people trying to output stuff to the same directory.
// However, that screws with umoci, because it sees an empty dir as an
// invalid image. the bug we're trying to fix <hair on fire>right
// now</hair on fire> is multiple invocations on a roots dir, so this
// is good enough.
for _, lock := range []*os.File{ls.stackerDir, ls.rootsDir} {
if lock != nil {
lock.Close()
}
}
ls.stackerDir = nil
ls.rootsDir = nil
}
func lock(config types.StackerConfig) (*StackerLocks, error) {
ls := &StackerLocks{}
var err error
ls.stackerDir, err = acquireLock(path.Join(config.StackerDir, lockPath))
if err != nil {
return nil, err
}
ls.rootsDir, err = acquireLock(path.Join(config.RootFSDir, lockPath))
if err != nil {
ls.Unlock()
return nil, err
}
return ls, nil
}