lockFile takes an exclusive advisory lock on " .lock", creating the lock file (and any missing parent directory) if needed. The returned closure releases the lock and closes the descriptor; defer it in the caller. The lock file is intentionally never renamed or deleted: doing so would let two
(path string)
| 297 | // would let two processes lock different inodes for the same logical |
| 298 | // resource and lose mutual exclusion. It's a long-lived sentinel. |
| 299 | func lockFile(path string) (func(), error) { |
| 300 | lockPath := path + ".lock" |
| 301 | if dir := filepath.Dir(lockPath); dir != "" { |
| 302 | if err := os.MkdirAll(dir, 0o700); err != nil { |
| 303 | return nil, fmt.Errorf("creating lock directory %q: %w", dir, err) |
| 304 | } |
| 305 | } |
| 306 | f, err := os.OpenFile(lockPath, os.O_RDWR|os.O_CREATE, 0o600) |
| 307 | if err != nil { |
| 308 | return nil, fmt.Errorf("opening lock file %q: %w", lockPath, err) |
| 309 | } |
| 310 | if err := lockExclusive(f); err != nil { |
| 311 | f.Close() |
| 312 | return nil, fmt.Errorf("locking %q: %w", lockPath, err) |
| 313 | } |
| 314 | // Errors in the release path are ignored: the OS will release the |
| 315 | // lock when the descriptor is closed regardless, and a failure to |
| 316 | // close at this point can't usefully be propagated. |
| 317 | return func() { |
| 318 | _ = unlockFile(f) |
| 319 | _ = f.Close() |
| 320 | }, nil |
| 321 | } |
| 322 | |
| 323 | // writeJSON atomically writes entries to path as pretty-printed JSON. |
| 324 | // |
no test coverage detected