diff --git a/go.mod b/go.mod index 576009df6..61f2a17d8 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 81738e4f2..6663c1c8d 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/system/inputdevices1/inputdevices.go b/system/inputdevices1/inputdevices.go index 423941795..bf342384c 100644 --- a/system/inputdevices1/inputdevices.go +++ b/system/inputdevices1/inputdevices.go @@ -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) } diff --git a/system/inputdevices1/touchpad.go b/system/inputdevices1/touchpad.go index 6f1a77993..5aab5fee6 100644 --- a/system/inputdevices1/touchpad.go +++ b/system/inputdevices1/touchpad.go @@ -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 规则