Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ require (
github.com/godbus/dbus/v5 v5.1.0
github.com/jouyouyun/hardware v0.1.8
github.com/linuxdeepin/dde-api v0.0.0-20260511093853-07ca3f2f1232
github.com/linuxdeepin/go-dbus-factory v0.0.0-20260604114052-5398d61b0886
github.com/linuxdeepin/go-dbus-factory v0.0.0-20260804093636-709f57a20d4a
github.com/linuxdeepin/go-gir v0.0.0-20251204113853-1873b5530f50
github.com/linuxdeepin/go-lib v0.0.0-20260205120541-a1f572ce1442
github.com/linuxdeepin/go-x11-client v0.0.0-20240415051504-c8e43d028ff9
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/linuxdeepin/dde-api v0.0.0-20260511093853-07ca3f2f1232 h1:1jSUHQ4FlFGhhhLiCzbXvcH+JIW92gGLWOQExbl/6SI=
github.com/linuxdeepin/dde-api v0.0.0-20260511093853-07ca3f2f1232/go.mod h1:etFJ3bz0/U3wklOLPLn7A4DM5VBQJ4yHhHaWpCEzzV8=
github.com/linuxdeepin/go-dbus-factory v0.0.0-20260604114052-5398d61b0886 h1:5RFVYFt5HjQ4P/Q/zIK1cfmbuoNX+T1F3WbXn1Sh/6E=
github.com/linuxdeepin/go-dbus-factory v0.0.0-20260604114052-5398d61b0886/go.mod h1:dfpixHkqiijg3P7w5QArkMyC7+vlHcutN5R6zHIn8is=
github.com/linuxdeepin/go-dbus-factory v0.0.0-20260804093636-709f57a20d4a h1:OLlPMMm/LdxpSjGuZhmK0hkM6ovwzYuXVB2EIFmMofY=
github.com/linuxdeepin/go-dbus-factory v0.0.0-20260804093636-709f57a20d4a/go.mod h1:dfpixHkqiijg3P7w5QArkMyC7+vlHcutN5R6zHIn8is=
github.com/linuxdeepin/go-gir v0.0.0-20250812023606-b28aaee32ac9/go.mod h1:a0tox5vepTQu5iO6rdKc4diGT+fkyXZlRROM8ULEvaI=
github.com/linuxdeepin/go-gir v0.0.0-20251204113853-1873b5530f50 h1:CLVQdE+YgfvHD2EadN/0hPIhCbbu5cdGyDu4brW9pyw=
github.com/linuxdeepin/go-gir v0.0.0-20251204113853-1873b5530f50/go.mod h1:a0tox5vepTQu5iO6rdKc4diGT+fkyXZlRROM8ULEvaI=
Expand Down
7 changes: 6 additions & 1 deletion system/inputdevices1/inputdevices.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,12 @@ func (m *InputDevices) init() {
logger.Warning(err)
return
}
err = m.touchpad.setTouchpadEnable(v.Value().(bool))
enabled := v.Value().(bool)
// 启动恢复:直接根据 dconfig 重建 udev 规则文件。
// 不调用 setTouchpadEnable,因为 newTouchpad 已将 Enable 设为 dconfig 值,
// setPropEnable 会判定 changed=false 从而跳过 udev 写入,
// 导致强制关机后 udev 规则文件丢失但无法重建。
err = m.touchpad.setTouchpadEnableViaUdev(enabled)
if err != nil {
logger.Warning(err)
}
Expand Down
105 changes: 91 additions & 14 deletions system/inputdevices1/touchpad.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,25 +129,102 @@ func (t *Touchpad) setTouchpadEnableViaUdev(enabled bool) error {
return err
}
logger.Info("removed udev rule file:", udevRuleFile)
} else {
// 禁用:生成动态的 udev 规则内容
udevRuleContent := generateUdevRuleContent()

// 检查文件是否已存在且内容相同
existingContent, err := os.ReadFile(udevRuleFile)
if err == nil && string(existingContent) == udevRuleContent {
logger.Debug("udev rule file already exists with correct content, skip writing")
return nil
return t.refreshTouchpadDevices()
}

// 禁用:写入 udev 规则文件;若内容未变则无需刷新设备
changed, err := writeUdevRuleFile(generateUdevRuleContent())
if err != nil {
return err
}
if !changed {
return nil
}
return t.refreshTouchpadDevices()
}

// writeUdevRuleFile 以 0644 权限原子地写入 udev 规则文件,并确保数据与目录项落盘。
// 返回 changed 表示文件内容是否发生变化(true=已写入或新建,false=内容未变跳过写入)。
// 安全要点:
// - fast path 内容已正确时仍收窄权限,防止外部改宽的 0666 等遗留
// - 用 O_EXCL 原子探测是否为首次新建,避免 Stat/OpenFile TOCTOU 竞态
// - 先 Chmod 成功再 Truncate,避免权限修改失败时 O_TRUNC 已清空原内容
// - 文件数据 fsync 后,首次创建还需 fsync 父目录以保证目录项断电不丢
func writeUdevRuleFile(udevRuleContent string) (changed bool, err error) {
// 内容已正确时,仅做权限兜底与父目录 best-effort sync 后直接返回
if existingContent, rerr := os.ReadFile(udevRuleFile); rerr == nil &&
string(existingContent) == udevRuleContent {
if err := os.Chmod(udevRuleFile, 0644); err != nil {
return false, err
}
syncDirBestEffort(filepath.Dir(udevRuleFile))
logger.Debug("udev rule file already exists with correct content, skip writing")
return false, nil
}

// 创建或覆盖 udev 规则文件
if err := os.WriteFile(udevRuleFile, []byte(udevRuleContent), 0644); err != nil {
return err
// 尝试以 O_EXCL 独占创建,原子判断是否为首次新建,
// 避免 Stat/OpenFile 之间因并发删除导致的 TOCTOU 竞态使父目录漏 sync
f, err := os.OpenFile(udevRuleFile, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0644)
isNew := err == nil
if err != nil && !os.IsExist(err) {
return false, err
}
if os.IsExist(err) {
// 文件已存在,以 O_WRONLY 打开但不立即截断
f, err = os.OpenFile(udevRuleFile, os.O_WRONLY, 0644)
if err != nil {
return false, err
}
}
// 先收窄权限,成功后再截断写入,避免 Chmod 失败时 O_TRUNC 已清空原内容导致规则丢失
if err := f.Chmod(0644); err != nil {
f.Close()
return false, err
}
if err := f.Truncate(0); err != nil {
f.Close()
return false, err
}
if _, err := f.Write([]byte(udevRuleContent)); err != nil {
f.Close()
return false, err
}
// fsync 确保文件数据落盘,
// 防止强制关机(断电)时 page cache 丢失导致规则文件丢失
if err := f.Sync(); err != nil {
f.Close()
return false, err
}
if err := f.Close(); err != nil {
return false, err
}
// 首次创建文件时,还需 fsync 父目录以确保目录项落盘,
// 否则断电后文件数据虽已持久但目录项丢失,规则文件仍会缺失
if isNew {
dir, err := os.Open(filepath.Dir(udevRuleFile))
if err != nil {
return false, err
}
err = dir.Sync()
dir.Close()
if err != nil {
return false, err
}
logger.Info("created udev rule file:", udevRuleFile)
}
logger.Info("created udev rule file:", udevRuleFile)
return true, nil
}

return t.refreshTouchpadDevices()
// syncDirBestEffort 尽力同步目录元数据,失败仅记录不返回错误。
func syncDirBestEffort(dir string) {
d, err := os.Open(dir)
if err != nil {
return
}
defer d.Close()
if err := d.Sync(); err != nil {
logger.Warning("failed to sync parent directory:", err)
}
}

// reloadUdevRules 重新加载 udev 规则
Expand Down
Loading