Merge pull request #92 from entireio/fix/tokenstore-windows-flock · Entire
Merge pull request #92 from entireio/fix/tokenstore-windows-flock
f3e7aab→main·
Soph·4w ago·4 files·+92 added/-27 removed
Make the token-store file lock compile and work on Windows
Changes
4
Mgo.mod+1/-1
internal/auth
Mtokenstore.go+3/-26
Atokenstore_lock_unix.go+35
Atokenstore_lock_windows.go+53
9 unmodified lines
10
11
12
13
14
15
16
15 unmodified lines
32
33
34
34
35
36
9 unmodified lines
github.com/spf13/cobra v1.10.2
github.com/stretchr/testify v1.11.1
github.com/zalando/go-keyring v0.2.8
golang.org/x/sys v0.44.0
)
require (
15 unmodified lines
golang.org/x/crypto v0.51.0 // indirect
golang.org/x/net v0.54.0 // indirect
golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.44.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
Mgo.mod+1/-1
4 unmodified lines
5
6
7
8
8
9
10
115 unmodified lines
126
127
128
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
129
130
131
4 unmodified lines
"fmt"
"os"
"path/filepath"
"syscall"
"github.com/zalando/go-keyring"
)
115 unmodified lines
return nil
}
// flockShared acquires a shared (read) lock on path+.lock.
func flockShared(path string) (func(), error) {
return flockOpen(path+".lock", syscall.LOCK_SH)
}
// flockExclusive acquires an exclusive (write) lock on path+.lock.
func flockExclusive(path string) (func(), error) {
return flockOpen(path+".lock", syscall.LOCK_EX)
}
func flockOpen(lockPath string, how int) (func(), error) {
f, err := os.OpenFile(lockPath, os.O_CREATE|os.O_RDWR, 0o600)
if err != nil {
return nil, fmt.Errorf("open lock file: %w", err)
}
if err := syscall.Flock(int(f.Fd()), how); err != nil {
f.Close()
return nil, fmt.Errorf("acquire file lock: %w", err)
}
return func() {
//nolint:errcheck // unlock errors on close are not actionable
syscall.Flock(int(f.Fd()), syscall.LOCK_UN)
f.Close()
}, nil
}
// flockShared / flockExclusive are defined per-platform: tokenstore_lock_unix.go
// uses syscall.Flock, tokenstore_lock_windows.go provides a compiling fallback
// (syscall.Flock and the LOCK_* constants do not exist on Windows).
Minternal/auth/tokenstore.go+3/-26
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
//go:build !windows
package auth
import ( "fmt" "os" "syscall" )
// flockShared acquires a shared (read) lock on path+.lock. func flockShared(path string) (func(), error) { return flockOpen(path+".lock", syscall.LOCK_SH) }
func flockOpen(lockPath string, how int) (func(), error) { f, err := os.OpenFile(lockPath, os.O_CREATE|os.O_RDWR, 0o600) if err != nil { return nil, fmt.Errorf("open lock file: %w", err) } if err := syscall.Flock(int(f.Fd()), how); err != nil { f.Close() return nil, fmt.Errorf("acquire file lock: %w", err) } return func() { //nolint:errcheck // unlock errors on close are not actionable syscall.Flock(int(f.Fd()), syscall.LOCK_UN) f.Close() }, nil }
Ainternal/auth/tokenstore_lock_unix.go+35
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
//go:build windows
package auth
import ( "fmt" "os"
"golang.org/x/sys/windows" )
// Windows has no flock(2); use LockFileEx on a dedicated ".lock" file for the // same advisory, interprocess mutual exclusion the Unix path gets from flock. // writeFileToken relies on this to serialize its read-modify-write (and the // shared temp-file write that precedes the rename), so a no-op would let // concurrent logins/refreshes lose an update.
// flockShared acquires a shared (read) lock on path+.lock. func flockShared(path string) (func(), error) { return flockOpen(path+".lock", 0) }
// flockExclusive acquires an exclusive (write) lock on path+.lock. func flockExclusive(path string) (func(), error) { return flockOpen(path+".lock", windows.LOCKFILE_EXCLUSIVE_LOCK) }
func flockOpen(lockPath string, flags uint32) (func(), error) { f, err := os.OpenFile(lockPath, os.O_CREATE|os.O_RDWR, 0o600) if err != nil { return nil, fmt.Errorf("open lock file: %w", err) } // Lock the entire file range, blocking until the lock is available // (no LOCKFILE_FAIL_IMMEDIATELY), matching flock's blocking semantics. // // os.OpenFile yields a synchronous handle (Go does not pass // FILE_FLAG_OVERLAPPED), so LockFileEx blocks until the lock is granted and // never returns ERROR_IO_PENDING — that pending/GetOverlappedResult path // only applies to handles opened for asynchronous I/O. Treating any error // as failure is therefore correct here; this matches the long-standing // github.com/gofrs/flock implementation. if err := windows.LockFileEx(windows.Handle(f.Fd()), flags, 0, maxUint32, maxUint32, new(windows.Overlapped)); err != nil { f.Close() return nil, fmt.Errorf("acquire file lock: %w", err) } return func() { //nolint:errcheck // unlock errors on close are not actionable windows.UnlockFileEx(windows.Handle(f.Fd()), 0, maxUint32, maxUint32, new(windows.Overlapped)) f.Close() }, nil }
const maxUint32 = ^uint32(0)
Ainternal/auth/tokenstore_lock_windows.go+53