1 // Copyright 2015 Tim Heckman. All rights reserved.
2 // Copyright 2018-2025 The Gofrs. All rights reserved.
3 // Use of this source code is governed by the BSD 3-Clause
4 // license that can be found in the LICENSE file.
5 6 //go:build windows
7 8 package flock
9 10 import (
11 "errors"
12 13 "golang.org/x/sys/windows"
14 )
15 16 // Use of 0x00000000 for the shared lock is a guess based on some the MS Windows `LockFileEX` docs,
17 // which document the `LOCKFILE_EXCLUSIVE_LOCK` flag as:
18 //
19 // > The function requests an exclusive lock. Otherwise, it requests a shared lock.
20 //
21 // https://msdn.microsoft.com/en-us/library/windows/desktop/aa365203(v=vs.85).aspx
22 const winLockfileSharedLock = 0x00000000
23 24 // ErrorLockViolation is the error code returned from the Windows syscall when a lock would block,
25 // and you ask to fail immediately.
26 //
27 //nolint:errname // It should be renamed to `ErrLockViolation`.
28 const ErrorLockViolation windows.Errno = 0x21 // 33
29 30 // Lock is a blocking call to try and take an exclusive file lock.
31 // It will wait until it is able to obtain the exclusive file lock.
32 // It's recommended that TryLock() be used over this function.
33 // This function may block the ability to query the current Locked() or RLocked() status due to a RW-mutex lock.
34 //
35 // If we are already locked, this function short-circuits and
36 // returns immediately assuming it can take the mutex lock.
37 func (f *Flock) Lock() error {
38 return f.lock(&f.l, windows.LOCKFILE_EXCLUSIVE_LOCK)
39 }
40 41 // RLock is a blocking call to try and take a shared file lock.
42 // It will wait until it is able to obtain the shared file lock.
43 // It's recommended that TryRLock() be used over this function.
44 // This function may block the ability to query the current Locked() or RLocked() status due to a RW-mutex lock.
45 //
46 // If we are already locked, this function short-circuits and
47 // returns immediately assuming it can take the mutex lock.
48 func (f *Flock) RLock() error {
49 return f.lock(&f.r, winLockfileSharedLock)
50 }
51 52 func (f *Flock) lock(locked *bool, flag uint32) error {
53 f.m.Lock()
54 defer f.m.Unlock()
55 56 if *locked {
57 return nil
58 }
59 60 if f.fh == nil {
61 if err := f.setFh(f.flag); err != nil {
62 return err
63 }
64 65 defer f.ensureFhState()
66 }
67 68 err := windows.LockFileEx(windows.Handle(f.fh.Fd()), flag, 0, 1, 0, &windows.Overlapped{})
69 if err != nil && !errors.Is(err, windows.Errno(0)) {
70 return err
71 }
72 73 *locked = true
74 75 return nil
76 }
77 78 // Unlock is a function to unlock the file.
79 // This file takes a RW-mutex lock,
80 // so while it is running the Locked() and RLocked() functions will be blocked.
81 //
82 // This function short-circuits if we are unlocked already.
83 // If not, it calls UnlockFileEx() on the file and closes the file descriptor.
84 // It does not remove the file from disk.
85 // It's up to your application to do.
86 func (f *Flock) Unlock() error {
87 f.m.Lock()
88 defer f.m.Unlock()
89 90 // if we aren't locked or if the lockfile instance is nil
91 // just return a nil error because we are unlocked
92 if (!f.l && !f.r) || f.fh == nil {
93 return nil
94 }
95 96 // mark the file as unlocked
97 err := windows.UnlockFileEx(windows.Handle(f.fh.Fd()), 0, 1, 0, &windows.Overlapped{})
98 if err != nil && !errors.Is(err, windows.Errno(0)) {
99 return err
100 }
101 102 f.reset()
103 104 return nil
105 }
106 107 // TryLock is the preferred function for taking an exclusive file lock.
108 // This function does take a RW-mutex lock before it tries to lock the file,
109 // so there is the possibility that this function may block for a short time
110 // if another goroutine is trying to take any action.
111 //
112 // The actual file lock is non-blocking.
113 // If we are unable to get the exclusive file lock,
114 // the function will return false instead of waiting for the lock.
115 // If we get the lock, we also set the *Flock instance as being exclusive-locked.
116 func (f *Flock) TryLock() (bool, error) {
117 return f.try(&f.l, windows.LOCKFILE_EXCLUSIVE_LOCK)
118 }
119 120 // TryRLock is the preferred function for taking a shared file lock.
121 // This function does take a RW-mutex lock before it tries to lock the file,
122 // so there is the possibility that this function may block for a short time if another goroutine is trying to take any action.
123 //
124 // The actual file lock is non-blocking.
125 // If we are unable to get the shared file lock,
126 // the function will return false instead of waiting for the lock.
127 // If we get the lock, we also set the *Flock instance as being shared-locked.
128 func (f *Flock) TryRLock() (bool, error) {
129 return f.try(&f.r, winLockfileSharedLock)
130 }
131 132 func (f *Flock) try(locked *bool, flag uint32) (bool, error) {
133 f.m.Lock()
134 defer f.m.Unlock()
135 136 if *locked {
137 return true, nil
138 }
139 140 if f.fh == nil {
141 if err := f.setFh(f.flag); err != nil {
142 return false, err
143 }
144 145 defer f.ensureFhState()
146 }
147 148 err := windows.LockFileEx(windows.Handle(f.fh.Fd()), flag|windows.LOCKFILE_FAIL_IMMEDIATELY, 0, 1, 0, &windows.Overlapped{})
149 if err != nil && !errors.Is(err, windows.Errno(0)) {
150 if errors.Is(err, ErrorLockViolation) || errors.Is(err, windows.ERROR_IO_PENDING) {
151 return false, nil
152 }
153 154 return false, err
155 }
156 157 *locked = true
158 159 return true, nil
160 }
161