Files
dnshelper/lockfile/lockfile.go
T
dyoder f0ce5a4042 feat: Implement platform-specific hosts file path retrieval
- Created a new `platform` package to handle OS-specific hosts file paths.
- Added implementations for macOS, Linux, and Windows to retrieve the hosts file path.
- Introduced tests for the `GetHostsFilePath` function to ensure correct behavior across platforms.

feat: Add DNS resolver for IP lookups

- Implemented a `resolver` package for performing DNS lookups.
- Created a `DNSResolver` struct with a `LookupIP` method to handle A-record lookups and CNAME resolution.
- Added comprehensive tests for various DNS scenarios, including CNAME chains and error handling.

chore: Refactor project structure and update dependencies

- Restructured the project to follow Go's standard package layout.
- Updated `go.mod` to Go 1.20 and added `golang.org/x/net` dependency.
- Removed old source files and ensured a clean build with all tests passing.
2026-03-03 18:37:02 -05:00

63 lines
1.6 KiB
Go

package lockfile
import (
"fmt"
"os"
"path/filepath"
"time"
)
const (
lockFileName = ".dns-helper.lock"
staleTimeout = 2 * time.Minute
retryInterval = 200 * time.Millisecond
maxWait = 2 * time.Second
)
// Lock represents an acquired lock file that can be released.
type Lock interface {
Release() error
}
type fileLock struct {
path string
}
// Release deletes the lock file. It is safe to call more than once.
func (l *fileLock) Release() error {
err := os.Remove(l.path)
if err != nil && os.IsNotExist(err) {
return nil
}
return err
}
// Acquire creates a lock file in dir and returns a Lock that can release it.
// It waits up to maxWait for a stale or otherwise held lock to clear.
func Acquire(dir string) (Lock, error) {
lockPath := filepath.Join(dir, lockFileName)
deadline := time.Now().Add(maxWait)
for {
f, err := os.OpenFile(lockPath, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0644)
if err == nil {
// Write PID for debugging (not used for staleness detection)
fmt.Fprintf(f, "%d\n", os.Getpid())
f.Close()
return &fileLock{path: lockPath}, nil
}
if !os.IsExist(err) {
return nil, fmt.Errorf("creating lock file %s: %w", lockPath, err)
}
// Lock file exists — check staleness
info, statErr := os.Stat(lockPath)
if statErr == nil && time.Since(info.ModTime()) > staleTimeout {
os.Remove(lockPath)
continue // Retry after breaking stale lock
}
if time.Now().After(deadline) {
return nil, fmt.Errorf("could not acquire lock file %s: another instance may be running", lockPath)
}
time.Sleep(retryInterval)
}
}