@@ -4,13 +4,78 @@ package lockutil
44
55import (
66 "errors"
7+ "fmt"
78 "os"
9+ "path/filepath"
10+ "strings"
811
912 "golang.org/x/sys/unix"
1013)
1114
1215type platformLockState struct {}
1316
17+ func openLockFileAt (root , relative , displayPath string ) (_ * os.File , resultErr error ) {
18+ parentFD , err := unix .Open (root , unix .O_RDONLY | unix .O_DIRECTORY | unix .O_CLOEXEC , 0 )
19+ if err != nil {
20+ return nil , fmt .Errorf ("open lock root: %w" , err )
21+ }
22+ defer func () {
23+ if parentFD >= 0 {
24+ resultErr = errors .Join (resultErr , unix .Close (parentFD ))
25+ }
26+ }()
27+
28+ components := strings .Split (filepath .Clean (relative ), string (filepath .Separator ))
29+ for _ , component := range components [:len (components )- 1 ] {
30+ nextFD , openErr := unix .Openat (parentFD , component , unix .O_RDONLY | unix .O_DIRECTORY | unix .O_CLOEXEC | unix .O_NOFOLLOW , 0 )
31+ if openErr != nil {
32+ return nil , fmt .Errorf ("open lock path component %q: %w" , component , openErr )
33+ }
34+ if closeErr := unix .Close (parentFD ); closeErr != nil {
35+ _ = unix .Close (nextFD )
36+ parentFD = - 1
37+ return nil , fmt .Errorf ("close lock path component: %w" , closeErr )
38+ }
39+ parentFD = nextFD
40+ }
41+
42+ name := components [len (components )- 1 ]
43+ fd := - 1
44+ // Darwin can transiently return ENOENT when many callers race to create the
45+ // same O_NOFOLLOW entry. The bound parent handle cannot be redirected, so a
46+ // bounded retry preserves first-use contention without weakening no-follow.
47+ for attempt := 0 ; attempt < 10 ; attempt ++ {
48+ fd , err = unix .Openat (parentFD , name , unix .O_CREAT | unix .O_RDWR | unix .O_CLOEXEC | unix .O_NOFOLLOW , 0o600 )
49+ if ! errors .Is (err , unix .ENOENT ) {
50+ break
51+ }
52+ }
53+ if err != nil {
54+ return nil , err
55+ }
56+ file := os .NewFile (uintptr (fd ), displayPath )
57+ if file == nil {
58+ _ = unix .Close (fd )
59+ return nil , errors .New ("convert lock handle to file" )
60+ }
61+ var stat unix.Stat_t
62+ if err := unix .Fstat (fd , & stat ); err != nil {
63+ return nil , errors .Join (fmt .Errorf ("inspect lock file: %w" , err ), file .Close ())
64+ }
65+ if stat .Mode & unix .S_IFMT != unix .S_IFREG {
66+ return nil , errors .Join (errors .New ("refusing non-regular lock file" ), file .Close ())
67+ }
68+ if stat .Nlink != 1 {
69+ return nil , errors .Join (errors .New ("refusing multiply-linked lock file" ), file .Close ())
70+ }
71+ if err := unix .Close (parentFD ); err != nil {
72+ parentFD = - 1
73+ return nil , errors .Join (fmt .Errorf ("close lock parent: %w" , err ), file .Close ())
74+ }
75+ parentFD = - 1
76+ return file , nil
77+ }
78+
1479func tryLockFile (file * os.File ) (platformLockState , bool , error ) {
1580 err := unix .Flock (int (file .Fd ()), unix .LOCK_EX | unix .LOCK_NB )
1681 if err == nil {
0 commit comments