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

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