staros/files_unix.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

185 lines
3.6 KiB
Go

//go:build linux
// +build linux
package staros
import (
"golang.org/x/sys/unix"
"os"
"syscall"
"time"
)
type FileLock struct {
fd int
filepath string
locked bool
}
func timespecToTime(ts syscall.Timespec) time.Time {
return time.Unix(int64(ts.Sec), int64(ts.Nsec))
}
func GetFileCreationTime(fileinfo os.FileInfo) time.Time {
if fileinfo == nil {
return time.Time{}
}
// Linux os.FileInfo/syscall.Stat_t does not expose a stable birth time.
// Returning ctime here would be wrong because it tracks inode changes.
return time.Time{}
}
func GetFileAccessTime(fileinfo os.FileInfo) time.Time {
if fileinfo == nil {
return time.Time{}
}
if stat, ok := fileinfo.Sys().(*syscall.Stat_t); ok && stat != nil {
return timespecToTime(stat.Atim)
}
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
}
stat, ok := info.Sys().(*syscall.Stat_t)
if !ok || stat == nil {
return errUnsupportedFileInfo
}
atime := timespecToTime(stat.Atim)
mtime := info.ModTime()
return setFileTimes(file.Name(), 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)
}
func setFileTimes(path string, atime, mtime time.Time) error {
ts := [2]unix.Timespec{
unix.NsecToTimespec(atime.UnixNano()),
unix.NsecToTimespec(mtime.UnixNano()),
}
return unix.UtimesNanoAt(unix.AT_FDCWD, path, ts[:], unix.AT_SYMLINK_NOFOLLOW)
}
func (f *FileLock) openFileForLock() error {
fd, err := syscall.Open(f.filepath, syscall.O_CREAT|syscall.O_RDONLY, 0600)
if err != nil {
return err
}
f.fd = fd
return nil
}
func (f *FileLock) Lock(Exclusive bool) error {
var lockType int
if Exclusive {
lockType = syscall.LOCK_EX
} else {
lockType = syscall.LOCK_SH
}
return f.lockWithFlags(lockType)
}
func (f *FileLock) LockNoBlocking(Exclusive bool) error {
var lockType int
if Exclusive {
lockType = syscall.LOCK_EX
} else {
lockType = syscall.LOCK_SH
}
return f.lockWithFlags(lockType | syscall.LOCK_NB)
}
func (f *FileLock) lockWithFlags(lockType int) error {
if f.locked {
return ERR_ALREADY_LOCKED
}
if err := f.openFileForLock(); err != nil {
return err
}
err := syscall.Flock(f.fd, lockType)
if err != nil {
_ = syscall.Close(f.fd)
f.fd = 0
if err == syscall.EWOULDBLOCK {
return ERR_ALREADY_LOCKED
}
return err
}
f.locked = true
return nil
}
func (f *FileLock) Unlock() error {
if f == nil || !f.locked {
return errFileLockNotLocked
}
err := syscall.Flock(f.fd, syscall.LOCK_UN)
if err != nil {
return err
}
if err := syscall.Close(f.fd); err != nil {
return err
}
f.fd = 0
f.locked = false
return nil
}
func (f *FileLock) LockWithTimeout(tm time.Duration, Exclusive bool) error {
if f.locked {
return ERR_ALREADY_LOCKED
}
var lockType int
if Exclusive {
lockType = syscall.LOCK_EX
} else {
lockType = syscall.LOCK_SH
}
if tm < 0 {
return f.Lock(Exclusive)
}
deadline := time.Now().Add(tm)
for {
err := f.lockWithFlags(lockType | syscall.LOCK_NB)
if err == nil {
return nil
}
if err != ERR_ALREADY_LOCKED {
return err
}
if !time.Now().Before(deadline) {
return ERR_TIMEOUT
}
sleep := time.Millisecond * 10
if remaining := time.Until(deadline); remaining < sleep {
sleep = remaining
}
if sleep > 0 {
time.Sleep(sleep)
}
}
}