Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,19 @@ jobs:
go-version: "1.26.x"
check-latest: true
- run: go test ./...

cross-compile:
name: cross compile release targets
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "1.26.x"
check-latest: true
- name: Build every goreleaser platform
run: |
for target in linux/amd64 linux/arm64 darwin/amd64 darwin/arm64 windows/amd64 windows/arm64; do
GOOS=${target%/*} GOARCH=${target#*/} CGO_ENABLED=0 go build ./... || exit 1
echo "ok $target"
done
3 changes: 3 additions & 0 deletions flock/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// Package flock wraps advisory file locks for safe concurrent appends.
// Unix builds use flock(2); Windows builds use LockFileEx.
package flock
3 changes: 2 additions & 1 deletion flock/flock.go → flock/flock_unix.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// Package flock wraps advisory POSIX file locks for safe concurrent appends.
//go:build unix

package flock

import (
Expand Down
65 changes: 65 additions & 0 deletions flock/flock_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
//go:build windows

package flock

import (
"errors"
"fmt"
"os"

"golang.org/x/sys/windows"
)

// Lock holds an exclusive lock on a file handle.
// It is acquired with Acquire and released by Close.
type Lock struct {
f *os.File
}

// Acquire opens the lock file at path and takes an exclusive lock.
// The lock file is created with mode 0o600 if it does not exist.
// A non-blocking attempt runs first; when another process holds the lock a
// single notice is printed to stderr before falling back to a blocking wait,
// so users know why the command has paused.
func Acquire(path string) (*Lock, error) {
f, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, 0o600) //nolint:gosec // Lock path derives from the vault directory.
if err != nil {
return nil, fmt.Errorf("open lock file: %w", err)
}
err = lockHandle(f, windows.LOCKFILE_EXCLUSIVE_LOCK|windows.LOCKFILE_FAIL_IMMEDIATELY)
if err == nil {
return &Lock{f: f}, nil
}
if !errors.Is(err, windows.ERROR_LOCK_VIOLATION) {
_ = f.Close()
return nil, fmt.Errorf("lock %s: %w", path, err)
}
fmt.Fprintln(os.Stderr, "waiting for vault lock (another midden command is running)")
if err := lockHandle(f, windows.LOCKFILE_EXCLUSIVE_LOCK); err != nil {
_ = f.Close()
return nil, fmt.Errorf("lock %s: %w", path, err)
}
return &Lock{f: f}, nil
}

// Close releases the lock and closes the underlying file handle.
// Subsequent calls are no-ops.
func (l *Lock) Close() error {
if l == nil || l.f == nil {
return nil
}
defer func() { l.f = nil }()
if err := windows.UnlockFileEx(windows.Handle(l.f.Fd()), 0, 1, 0, new(windows.Overlapped)); err != nil {
_ = l.f.Close()
return fmt.Errorf("unlock: %w", err)
}
if err := l.f.Close(); err != nil {
return fmt.Errorf("close lock file: %w", err)
}
return nil
}

// lockHandle locks the first byte of the file with the given LockFileEx flags.
func lockHandle(f *os.File, flags uint32) error {
return windows.LockFileEx(windows.Handle(f.Fd()), flags, 0, 1, 0, new(windows.Overlapped))
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ require (
github.com/google/go-cmp v0.7.0
github.com/spf13/cobra v1.10.2
github.com/zalando/go-keyring v0.2.8
golang.org/x/sys v0.46.0
golang.org/x/term v0.44.0
gopkg.in/yaml.v3 v3.0.1
)
Expand Down Expand Up @@ -36,6 +37,5 @@ require (
github.com/xanzy/ssh-agent v0.3.3 // indirect
golang.org/x/crypto v0.53.0 // indirect
golang.org/x/net v0.56.0 // indirect
golang.org/x/sys v0.46.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
)
Loading