carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1//go:build unix
2
3package hostguard
4
5import (
6 "context"
7 "errors"
8 "os"
9 "time"
10
11 "golang.org/x/sys/unix"
12)
13
14func acquire(ctx context.Context, dir, host string, verifyOwner ...bool) (func(), error) {
15 if ctx == nil {
16 return nil, ErrInvalidInput
17 }
18 if err := ctx.Err(); err != nil {
19 return nil, err
20 }
21 if err := validateFileHost(host); err != nil {
22 return nil, err
23 }
24 dirFD, err := openDirectory(dir)
25 if err != nil {
26 return nil, err
27 }
28 defer unix.Close(dirFD)
29 var parent unix.Stat_t
30 if err := unix.Fstat(dirFD, &parent); err != nil {
31 return nil, err
32 }
33 unsafe := func(st unix.Stat_t) bool {
34 return st.Mode&unix.S_IFMT != unix.S_IFREG || (len(verifyOwner) == 1 && verifyOwner[0] && st.Uid != 0 && st.Uid != parent.Uid)
35 }
36 var prior unix.Stat_t
37 if err := unix.Fstatat(dirFD, host+".lock", &prior, unix.AT_SYMLINK_NOFOLLOW); err == nil && unsafe(prior) {
38 return nil, ErrUnsafeFile
39 }
40 flags := unix.O_RDWR | unix.O_CREAT | unix.O_NOFOLLOW | unix.O_CLOEXEC | unix.O_NONBLOCK
41 fd, err := unix.Openat(dirFD, host+".lock", flags|unix.O_EXCL, 0660)
42 created := err == nil
43 if errors.Is(err, unix.EEXIST) {
44 fd, err = unix.Openat(dirFD, host+".lock", flags, 0660)
45 }
46 if err != nil {
47 if errors.Is(err, unix.ELOOP) || errors.Is(err, unix.EISDIR) {
48 return nil, ErrUnsafeFile
49 }
50 return nil, err
51 }
52 f := os.NewFile(uintptr(fd), host+".lock")
53 fail := func(err error) (func(), error) { f.Close(); return nil, err }
54 var st unix.Stat_t
55 if err := unix.Fstat(fd, &st); err != nil {
56 return fail(err)
57 }
58 if unsafe(st) {
59 return fail(ErrUnsafeFile)
60 }
61 if created && len(verifyOwner) == 1 && verifyOwner[0] && os.Geteuid() == 0 {
62 if err := unix.Fchown(fd, -1, int(parent.Gid)); err != nil {
63 return fail(err)
64 }
65 }
66 if created || st.Mode&07777 != 0660 {
67 if err := unix.Fchmod(fd, 0660); err != nil {
68 return fail(err)
69 }
70 }
71 delay := 10 * time.Millisecond
72 for {
73 if err := ctx.Err(); err != nil {
74 return fail(err)
75 }
76 err := unix.Flock(int(f.Fd()), unix.LOCK_EX|unix.LOCK_NB)
77 if err == nil {
78 unlock := func() { _ = unix.Flock(int(f.Fd()), unix.LOCK_UN); _ = f.Close() }
79 if err := ctx.Err(); err != nil {
80 unlock()
81 return nil, err
82 }
83 return unlock, nil
84 }
85 if !errors.Is(err, unix.EWOULDBLOCK) && !errors.Is(err, unix.EAGAIN) && !errors.Is(err, unix.EINTR) {
86 return fail(err)
87 }
88 timer := time.NewTimer(delay)
89 select {
90 case <-ctx.Done():
91 timer.Stop()
92 return fail(ctx.Err())
93 case <-timer.C:
94 }
95 delay = min(2*delay, 100*time.Millisecond)
96 }
97}
98