//go:build windows // +build windows package staros import ( "b612.me/win32api" "golang.org/x/sys/windows" "os" "syscall" "time" ) type FileLock struct { filepath string handle win32api.HANDLE locked bool } func GetFileCreationTime(fileinfo os.FileInfo) time.Time { 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{} } func GetFileAccessTime(fileinfo os.FileInfo) time.Time { 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{} } func SetFileTimes(file *os.File, info os.FileInfo) { _ = SetFileTimesE(file, info) } func SetFileTimesbyTime(file *os.File) { _ = 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) } 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 { if f.locked { return ERR_ALREADY_LOCKED } var err error if err = f.openFileForLock(); err != nil { return err } event, err := win32api.CreateEventW(nil, true, false, nil) if err != nil { _ = f.closeHandle() 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 { f.locked = true return nil } if err != syscall.ERROR_IO_PENDING { _ = f.closeHandle() 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! f.locked = true return nil case syscall.WAIT_TIMEOUT: _ = f.closeHandle() return ERR_TIMEOUT default: _ = f.closeHandle() return err } } func (f *FileLock) Lock(Exclusive bool) error { var lockType win32api.DWORD if Exclusive { lockType = win32api.LOCKFILE_EXCLUSIVE_LOCK } else { lockType = 0 } return f.lockForTimeout(-1, lockType) } 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) } 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) } func (f *FileLock) Unlock() error { 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 }