staros/files_windows.go
starainrt d93a851d1b
feat: 完善 staros 系统能力并更新 wincmd 发布版依赖
- 重构 sysconf 为文档模型 INI Parser 与 Config Framework
- 强化 hosts 解析、插入校验、写回与异常输入处理
- 完善 StarCmd 生命周期、等待 API、流式输出与 IO 重定向
- 扩展跨平台文件时间、文件锁、内存、进程与网络能力
- 将 Windows 进程适配更新到 b612.me/wincmd v0.1.0
- 移除本地 wincmd/win32api replace,改用发布版依赖
- 将最低 Go 版本提升到 1.18
- 补充 hosts、sysconf、FileLock、StarCmd 与平台适配回归测试
2026-06-09 18:10:19 +08:00

218 lines
4.7 KiB
Go

//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
}