2026-06-09 18:10:19 +08:00
|
|
|
//go:build windows
|
2020-12-21 17:19:41 +08:00
|
|
|
// +build windows
|
|
|
|
|
|
|
|
|
|
package staros
|
|
|
|
|
|
|
|
|
|
import (
|
2021-09-01 11:01:51 +08:00
|
|
|
"b612.me/win32api"
|
2026-06-09 18:10:19 +08:00
|
|
|
"golang.org/x/sys/windows"
|
2020-12-21 17:19:41 +08:00
|
|
|
"os"
|
|
|
|
|
"syscall"
|
|
|
|
|
"time"
|
|
|
|
|
)
|
|
|
|
|
|
2021-09-01 11:01:51 +08:00
|
|
|
type FileLock struct {
|
|
|
|
|
filepath string
|
|
|
|
|
handle win32api.HANDLE
|
2026-06-09 18:10:19 +08:00
|
|
|
locked bool
|
2021-09-01 11:01:51 +08:00
|
|
|
}
|
|
|
|
|
|
2020-12-21 17:19:41 +08:00
|
|
|
func GetFileCreationTime(fileinfo os.FileInfo) time.Time {
|
2026-06-09 18:10:19 +08:00
|
|
|
if fileinfo == nil {
|
|
|
|
|
return time.Time{}
|
|
|
|
|
}
|
|
|
|
|
if data, ok := fileinfo.Sys().(*syscall.Win32FileAttributeData); ok && data != nil {
|
|
|
|
|
return time.Unix(0, data.CreationTime.Nanoseconds())
|
|
|
|
|
}
|
|
|
|
|
return time.Time{}
|
2020-12-21 17:19:41 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func GetFileAccessTime(fileinfo os.FileInfo) time.Time {
|
2026-06-09 18:10:19 +08:00
|
|
|
if fileinfo == nil {
|
|
|
|
|
return time.Time{}
|
|
|
|
|
}
|
|
|
|
|
if data, ok := fileinfo.Sys().(*syscall.Win32FileAttributeData); ok && data != nil {
|
|
|
|
|
return time.Unix(0, data.LastAccessTime.Nanoseconds())
|
|
|
|
|
}
|
|
|
|
|
return time.Time{}
|
2020-12-21 17:19:41 +08:00
|
|
|
}
|
2021-05-17 15:09:04 +08:00
|
|
|
|
2021-09-01 11:01:51 +08:00
|
|
|
func SetFileTimes(file *os.File, info os.FileInfo) {
|
2026-06-09 18:10:19 +08:00
|
|
|
_ = SetFileTimesE(file, info)
|
2021-05-17 15:09:04 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func SetFileTimesbyTime(file *os.File) {
|
2026-06-09 18:10:19 +08:00
|
|
|
_ = SetFileTimesbyTimeE(file)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func SetFileTimesE(file *os.File, info os.FileInfo) error {
|
|
|
|
|
if file == nil {
|
|
|
|
|
return errNilFile
|
|
|
|
|
}
|
|
|
|
|
if info == nil {
|
|
|
|
|
return errNilFileInfo
|
|
|
|
|
}
|
|
|
|
|
data, ok := info.Sys().(*syscall.Win32FileAttributeData)
|
|
|
|
|
if !ok || data == nil {
|
|
|
|
|
return errUnsupportedFileInfo
|
|
|
|
|
}
|
|
|
|
|
ctime := time.Unix(0, data.CreationTime.Nanoseconds())
|
|
|
|
|
atime := time.Unix(0, data.LastAccessTime.Nanoseconds())
|
|
|
|
|
mtime := info.ModTime()
|
|
|
|
|
return setFileTimes(file.Name(), ctime, atime, mtime)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func SetFileTimesByTimeE(file *os.File) error {
|
|
|
|
|
return SetFileTimesbyTimeE(file)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func SetFileTimesbyTimeE(file *os.File) error {
|
|
|
|
|
if file == nil {
|
|
|
|
|
return errNilFile
|
|
|
|
|
}
|
|
|
|
|
now := time.Now()
|
|
|
|
|
return setFileTimes(file.Name(), now, now, now)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func setFileTimes(path string, ctime, atime, mtime time.Time) error {
|
|
|
|
|
path16, err := windows.UTF16PtrFromString(path)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
handle, err := windows.CreateFile(
|
|
|
|
|
path16,
|
|
|
|
|
windows.FILE_WRITE_ATTRIBUTES,
|
|
|
|
|
windows.FILE_SHARE_READ|windows.FILE_SHARE_WRITE|windows.FILE_SHARE_DELETE,
|
|
|
|
|
nil,
|
|
|
|
|
windows.OPEN_EXISTING,
|
|
|
|
|
windows.FILE_FLAG_BACKUP_SEMANTICS,
|
|
|
|
|
0,
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
defer windows.CloseHandle(handle)
|
|
|
|
|
|
|
|
|
|
ctimeFt := windows.NsecToFiletime(ctime.UnixNano())
|
|
|
|
|
atimeFt := windows.NsecToFiletime(atime.UnixNano())
|
|
|
|
|
mtimeFt := windows.NsecToFiletime(mtime.UnixNano())
|
|
|
|
|
return windows.SetFileTime(handle, &ctimeFt, &atimeFt, &mtimeFt)
|
2021-06-04 10:44:53 +08:00
|
|
|
|
2021-09-01 11:01:51 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (f *FileLock) openFileForLock() error {
|
|
|
|
|
name, err := syscall.UTF16PtrFromString(f.filepath)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
handle, err := syscall.CreateFile(
|
|
|
|
|
name,
|
|
|
|
|
syscall.GENERIC_READ,
|
|
|
|
|
syscall.FILE_SHARE_READ,
|
|
|
|
|
nil,
|
|
|
|
|
syscall.OPEN_ALWAYS,
|
|
|
|
|
syscall.FILE_FLAG_OVERLAPPED|0x00000080,
|
|
|
|
|
0)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
f.handle = win32api.HANDLE(handle)
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (f *FileLock) lockForTimeout(timeout time.Duration, lockType win32api.DWORD) error {
|
2026-06-09 18:10:19 +08:00
|
|
|
if f.locked {
|
|
|
|
|
return ERR_ALREADY_LOCKED
|
|
|
|
|
}
|
2021-09-01 11:01:51 +08:00
|
|
|
var err error
|
|
|
|
|
if err = f.openFileForLock(); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
event, err := win32api.CreateEventW(nil, true, false, nil)
|
|
|
|
|
if err != nil {
|
2026-06-09 18:10:19 +08:00
|
|
|
_ = f.closeHandle()
|
2021-09-01 11:01:51 +08:00
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
myEvent := &syscall.Overlapped{HEvent: syscall.Handle(event)}
|
|
|
|
|
defer syscall.CloseHandle(myEvent.HEvent)
|
|
|
|
|
_, err = win32api.LockFileEx(f.handle, lockType, 0, 1, 0, myEvent)
|
|
|
|
|
if err == nil {
|
2026-06-09 18:10:19 +08:00
|
|
|
f.locked = true
|
2021-09-01 11:01:51 +08:00
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
if err != syscall.ERROR_IO_PENDING {
|
2026-06-09 18:10:19 +08:00
|
|
|
_ = f.closeHandle()
|
2021-09-01 11:01:51 +08:00
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
millis := uint32(syscall.INFINITE)
|
|
|
|
|
if timeout >= 0 {
|
|
|
|
|
millis = uint32(timeout.Nanoseconds() / 1000000)
|
|
|
|
|
}
|
|
|
|
|
s, err := syscall.WaitForSingleObject(myEvent.HEvent, millis)
|
|
|
|
|
switch s {
|
|
|
|
|
case syscall.WAIT_OBJECT_0:
|
|
|
|
|
// success!
|
2026-06-09 18:10:19 +08:00
|
|
|
f.locked = true
|
2021-09-01 11:01:51 +08:00
|
|
|
return nil
|
|
|
|
|
case syscall.WAIT_TIMEOUT:
|
2026-06-09 18:10:19 +08:00
|
|
|
_ = f.closeHandle()
|
2021-09-01 11:01:51 +08:00
|
|
|
return ERR_TIMEOUT
|
|
|
|
|
default:
|
2026-06-09 18:10:19 +08:00
|
|
|
_ = f.closeHandle()
|
2021-09-01 11:01:51 +08:00
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-01 13:52:43 +08:00
|
|
|
func (f *FileLock) Lock(Exclusive bool) error {
|
|
|
|
|
var lockType win32api.DWORD
|
|
|
|
|
if Exclusive {
|
|
|
|
|
lockType = win32api.LOCKFILE_EXCLUSIVE_LOCK
|
|
|
|
|
} else {
|
|
|
|
|
lockType = 0
|
|
|
|
|
}
|
2026-06-09 18:10:19 +08:00
|
|
|
return f.lockForTimeout(-1, lockType)
|
2021-09-01 11:01:51 +08:00
|
|
|
}
|
|
|
|
|
|
2021-09-01 13:52:43 +08:00
|
|
|
func (f *FileLock) LockWithTimeout(tm time.Duration, Exclusive bool) error {
|
|
|
|
|
var lockType win32api.DWORD
|
|
|
|
|
if Exclusive {
|
|
|
|
|
lockType = win32api.LOCKFILE_EXCLUSIVE_LOCK
|
|
|
|
|
} else {
|
|
|
|
|
lockType = 0
|
|
|
|
|
}
|
|
|
|
|
return f.lockForTimeout(tm, lockType)
|
2021-09-01 11:01:51 +08:00
|
|
|
}
|
|
|
|
|
|
2021-09-01 13:52:43 +08:00
|
|
|
func (f *FileLock) LockNoBlocking(Exclusive bool) error {
|
|
|
|
|
var lockType win32api.DWORD
|
|
|
|
|
if Exclusive {
|
|
|
|
|
lockType = win32api.LOCKFILE_EXCLUSIVE_LOCK
|
|
|
|
|
} else {
|
|
|
|
|
lockType = 0
|
|
|
|
|
}
|
|
|
|
|
return f.lockForTimeout(0, lockType|win32api.LOCKFILE_FAIL_IMMEDIATELY)
|
2021-09-01 11:01:51 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (f *FileLock) Unlock() error {
|
2026-06-09 18:10:19 +08:00
|
|
|
if f == nil || !f.locked {
|
|
|
|
|
return errFileLockNotLocked
|
|
|
|
|
}
|
|
|
|
|
if err := f.closeHandle(); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
f.locked = false
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (f *FileLock) closeHandle() error {
|
|
|
|
|
if f == nil || f.handle == 0 {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
err := syscall.Close(syscall.Handle(f.handle))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
f.handle = 0
|
|
|
|
|
return nil
|
2021-09-01 11:01:51 +08:00
|
|
|
}
|