Skip to content

fix: skip screen off if already in dpms off state#1148

Closed
fly602 wants to merge 1 commit into
linuxdeepin:masterfrom
fly602:master
Closed

fix: skip screen off if already in dpms off state#1148
fly602 wants to merge 1 commit into
linuxdeepin:masterfrom
fly602:master

Conversation

@fly602

@fly602 fly602 commented Jun 17, 2026

Copy link
Copy Markdown
Contributor
  1. Add a check at the beginning of the systemTurnOffScreen function to skip processing if the display is already in a DPMS off state
  2. Introduce a new isDpmsOff helper function to read the state from / tmp/dpms-state and determine if the screen is already off
  3. Adjust the ordering of suspend state constants for logical consistency, moving suspendStateLidClose before suspendStateFinish

Log: Optimized power button logic to avoid redundant screen-off commands

Influence:

  1. Test pressing the power button when the screen is already off via DPMS; verify the screen state does not change and no redundant actions occur
  2. Test pressing the power button when the screen is on; verify the screen turns off correctly
  3. Test the behavior after waking the system from suspend (lid open); ensure the screen state is correctly reported and handled
  4. Verify that the /tmp/dpms-state file is created and updated correctly on screen state changes
  5. Test the interaction between the power button and other screen off mechanisms (like idle timeout) to ensure no conflicts

fix: 如果屏幕已处于DPMS关闭状态则跳过屏幕关闭处理

  1. 在systemTurnOffScreen函数开头添加检查,如果显示器已处于DPMS off状态则 跳过处理
  2. 新增isDpmsOff辅助函数,通过读取/tmp/dpms-state文件来判断屏幕是否已 关闭
  3. 调整挂起状态常量的顺序以保持逻辑一致性,将suspendStateLidClose移到 suspendStateFinish之前

Log: 优化电源键逻辑,避免重复发送屏幕关闭命令

Influence:

  1. 测试在屏幕已通过DPMS关闭时按下电源键,验证屏幕状态不会改变且没有多余 操作
  2. 测试在屏幕开启时按下电源键,验证屏幕能正确关闭
  3. 测试从挂起状态唤醒(开盖)后的行为,确保屏幕状态能正确报告和处理
  4. 验证/tmp/dpms-state文件在屏幕状态变化时能被正确创建和更新
  5. 测试电源键与其他屏幕关闭机制(如空闲超时)的交互,确保没有冲突

PMS: BUG-364835

Summary by Sourcery

Skip redundant DPMS screen-off operations when the display is already off and align suspend state constants ordering

Bug Fixes:

  • Prevent duplicate DPMS off commands by checking the stored DPMS state before turning off the screen

Enhancements:

  • Reorder suspend state constants to reflect a more logical suspend lifecycle sequence

@deepin-ci-robot

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: fly602

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@sourcery-ai

sourcery-ai Bot commented Jun 17, 2026

Copy link
Copy Markdown
Reviewer's guide (collapsed on small PRs)

Reviewer's Guide

Adds a DPMS state check before turning off the screen, introduces a helper to read /tmp/dpms-state, and reorders suspend state constants for clearer semantics.

Sequence diagram for DPMS-aware systemTurnOffScreen

sequenceDiagram
    actor User
    participant PowerButton
    participant Manager
    participant isDpmsOff
    participant DPMS

    User->>PowerButton: press
    PowerButton->>Manager: systemTurnOffScreen
    Manager->>isDpmsOff: isDpmsOff
    isDpmsOff->>isDpmsOff: os.ReadFile_tmp_dpms_state
    isDpmsOff-->>Manager: dpmsOffFlag

    alt [dpmsOffFlag == true]
        Manager-->>PowerButton: return (no action)
    else [dpmsOffFlag == false]
        Manager->>DPMS: DPMS Off
        DPMS-->>Manager: screen off
        Manager->>DPMS: os.WriteFile_tmp_dpms_state
    end
Loading

File-Level Changes

Change Details Files
Short-circuit screen-off logic when DPMS already reports the display as off.
  • Add early return at the start of systemTurnOffScreen when the DPMS state file indicates the screen is already off
  • Ensure the existing DPMS-Off logic and logging only run when a transition from on to off is needed
  • Persist DPMS off state by writing "1" to /tmp/dpms-state after successfully turning the screen off
keybinding1/utils.go
Introduce a helper for reading the DPMS state from /tmp/dpms-state.
  • Add isDpmsOff helper that reads /tmp/dpms-state, logs a debug message on read errors, and returns false on failure
  • Normalize read contents by trimming whitespace and comparing against the string "1" to decide whether the screen is off
keybinding1/utils.go
Reorder suspend state constants for more logical progression of suspend events.
  • Move suspendStateLidClose to appear before suspendStateFinish in the iota-based constant block to better reflect the suspend lifecycle ordering
  • Keep all existing constant names and usages intact while changing only their relative order/values
keybinding1/utils.go

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@fly602 fly602 requested a review from mhduiy June 17, 2026 07:14

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue, and left some high level feedback:

  • Reordering the iota-based suspendState constants changes their numeric values; if these are persisted or used across processes, consider assigning explicit values or appending new states instead of reordering to avoid breaking existing consumers.
  • The /tmp/dpms-state path and magic value "1" are duplicated between writer and reader; consider extracting a shared constant and/or helper to avoid drift if the file location or format changes.
  • isDpmsOff treats any read error as 'not off' and logs only at debug; if a missing or unreadable state file should be handled differently from a deliberate 'screen on' state, consider distinguishing ENOENT and other errors and logging unexpected failures at a higher level.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Reordering the iota-based suspendState constants changes their numeric values; if these are persisted or used across processes, consider assigning explicit values or appending new states instead of reordering to avoid breaking existing consumers.
- The `/tmp/dpms-state` path and magic value `"1"` are duplicated between writer and reader; consider extracting a shared constant and/or helper to avoid drift if the file location or format changes.
- isDpmsOff treats any read error as 'not off' and logs only at debug; if a missing or unreadable state file should be handled differently from a deliberate 'screen on' state, consider distinguishing ENOENT and other errors and logging unexpected failures at a higher level.

## Individual Comments

### Comment 1
<location path="keybinding1/utils.go" line_range="33-34" />
<code_context>
 const (
 	suspendStateUnknown = iota + 1
 	suspendStateLidOpen
+	suspendStateLidClose
 	suspendStateFinish
 	suspendStateWakeup
</code_context>
<issue_to_address>
**issue (bug_risk):** Reordering iota-based suspend state constants may break any persisted or external uses of these numeric values

Inserting `suspendStateLidClose` here shifts the numeric values of all following states. If these integers are persisted (config/DB), sent over IPC, or shared across processes, this can cause regressions. If reordering is safe in this case, consider assigning explicit values to each constant so future reordering doesn’t change their numeric representation.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread keybinding1/utils.go
@fly602

fly602 commented Jun 17, 2026

Copy link
Copy Markdown
Contributor Author

/tmp/dpms-state 分别在keybinding和power这两个模块中都有读写,属于是共享一个状态标志,不适合写入内存中。
而且这个/tmp/dpms-state影响较弱,只有状态为1时会影响电源按钮逻辑,但是这个状态,会在power模块中刷新。

1. Add a check at the beginning of the systemTurnOffScreen function to
skip processing if the display is already in a DPMS off state
2. Introduce a new isDpmsOff helper function to read the state from /
tmp/dpms-state and determine if the screen is already off
3. Adjust the ordering of suspend state constants for logical
consistency, moving suspendStateLidClose before suspendStateFinish

Log: Optimized power button logic to avoid redundant screen-off commands

Influence:
1. Test pressing the power button when the screen is already off via
DPMS; verify the screen state does not change and no redundant actions
occur
2. Test pressing the power button when the screen is on; verify the
screen turns off correctly
3. Test the behavior after waking the system from suspend (lid open);
ensure the screen state is correctly reported and handled
4. Verify that the /tmp/dpms-state file is created and updated correctly
on screen state changes
5. Test the interaction between the power button and other screen off
mechanisms (like idle timeout) to ensure no conflicts

fix: 如果屏幕已处于DPMS关闭状态则跳过屏幕关闭处理

1. 在systemTurnOffScreen函数开头添加检查,如果显示器已处于DPMS off状态则
跳过处理
2. 新增isDpmsOff辅助函数,通过读取/tmp/dpms-state文件来判断屏幕是否已
关闭
3. 调整挂起状态常量的顺序以保持逻辑一致性,将suspendStateLidClose移到
suspendStateFinish之前

Log: 优化电源键逻辑,避免重复发送屏幕关闭命令

Influence:
1. 测试在屏幕已通过DPMS关闭时按下电源键,验证屏幕状态不会改变且没有多余
操作
2. 测试在屏幕开启时按下电源键,验证屏幕能正确关闭
3. 测试从挂起状态唤醒(开盖)后的行为,确保屏幕状态能正确报告和处理
4. 验证/tmp/dpms-state文件在屏幕状态变化时能被正确创建和更新
5. 测试电源键与其他屏幕关闭机制(如空闲超时)的交互,确保没有冲突

PMS: BUG-364835
@deepin-ci-robot

Copy link
Copy Markdown

deepin pr auto review

★ 总体评分:60分

■ 【总体评价】

代码实现了文件安全读写封装以修复符号链接攻击,但存在常量值意外变更导致的严重逻辑错误及未校验硬链接数的安全缺陷
逻辑错误因修改常量顺序扣15分,安全缺陷因未校验硬链接数扣15分并触发安全上限降至60分

■ 【详细分析】

  • 1.语法逻辑(存在严重错误)✕

keybinding1/utils.go 中,suspendStateLidClosesuspendStateFinish 的位置被互换,导致 iota 赋值发生改变。原来 suspendStateLidClose 为 3,suspendStateFinish 为 4,修改后两者值互换。若这些状态值被持久化存储或用于 DBus 通信,将引发严重的状态解析错误。此外,session/power1/power_save_plan.go 中的 restoreDpmsStateFile 函数使用 string(v) == "1" 判断状态,而 keybinding1/utils.go 中的 isDpmsOff 使用 bytes.Equal(bytes.TrimSpace(content), []byte("1")) 判断,两者对换行符的处理逻辑不一致,可能导致状态恢复失败。
潜在问题:常量值意外变更导致状态机错乱;状态判断逻辑不一致导致屏幕状态无法正确恢复
建议:撤销对 suspendStateLidClosesuspendStateFinish 顺序的无意义调整;统一两处 DPMS 状态读取的判断逻辑,均使用 bytes.TrimSpace 处理

  • 2.代码质量(一般)✕

新增的 fileutil 包封装良好,注释清晰,但在 keybinding1/utils.go 中混入了无关的常量顺序调整,降低了代码变更的可读性和审查友好度。此外,硬编码的 /tmp/dpms-state 路径在多个文件中重复出现。
潜在问题:无关代码修改增加了引入缺陷的风险;魔法字符串重复定义
建议:将 /tmp/dpms-state 提取为 fileutil 包或相关模块的常量;移除与本次安全修复无关的常量顺序调整

  • 3.代码性能(无性能问题)✓

SafeReadFileSafeWriteFile 在原有基础上增加了一次 Lstat 系统调用以验证文件类型。由于屏幕状态切换属于极低频操作,额外的系统调用开销完全可以忽略,整体无性能问题。
建议:维持现状

  • 4.代码安全(存在 1 个安全漏洞(中危1个))✕

漏洞对比统计:新增漏洞 1 个,减少漏洞 0 个,持平 0 个
代码成功修复了原有的 TOCTOU 竞争条件和符号链接跟随漏洞,但在通用安全写入函数中引入了新的硬链接攻击面,攻击者可在同文件系统内利用硬链接截断目标文件。

  • 安全漏洞1(中危):硬链接截断攻击 在 common/fileutil/fileutil.goSafeWriteFile 函数中,当文件已存在时,仅通过 Lstat 检查了是否为符号链接和普通文件,未检查硬链接数。在 Linux 中,若攻击者在 /tmp 目录下预先创建一个指向同文件系统内其他进程敏感文件的硬链接命名为 /tmp/dpms-stateSafeWriteFile 会将其识别为普通文件并使用 O_TRUNC 标志截断目标文件内容,导致敏感数据丢失或进程异常 ——非常重要

  • 建议:在 SafeWriteFileLstat 检查后,提取底层 unix.Stat_t 结构体,判断 Nlink 是否大于 1,若大于 1 则拒绝写入并返回错误;或者将状态文件迁移至用户私有目录(如 ~/.cache//run/user/uid/)以规避 /tmp 目录的全局可写风险

■ 【改进建议代码示例】

// common/fileutil/fileutil.go
package fileutil

import (
	"fmt"
	"io"
	"os"

	"golang.org/x/sys/unix"
)

// SafeWriteFile 安全写入文件,拒绝符号链接和硬链接
func SafeWriteFile(filename string, content []byte, perm os.FileMode) error {
	fi, err := os.Lstat(filename)
	if err != nil && !os.IsNotExist(err) {
		return err
	}
	if err == nil {
		// 文件已存在,必须是普通文件且无硬链接
		if fi.Mode()&os.ModeSymlink != 0 {
			return fmt.Errorf("file %q is a symlink, refusing to write", filename)
		}
		if !fi.Mode().IsRegular() {
			return fmt.Errorf("file %q is not a regular file", filename)
		}
		// 检查硬链接数,防止硬链接截断攻击
		stat, ok := fi.Sys().(*unix.Stat_t)
		if !ok {
			return fmt.Errorf("file %q failed to get underlying stat info", filename)
		}
		if stat.Nlink > 1 {
			return fmt.Errorf("file %q has hard links (%d), refusing to truncate", filename, stat.Nlink)
		}
		
		f, err := os.OpenFile(filename, unix.O_WRONLY|unix.O_TRUNC|unix.O_NOFOLLOW, perm)
		if err != nil {
			return err
		}
		defer f.Close()
		_, err = f.Write(content)
		return err
	}
	// 文件不存在,安全创建
	f, err := os.OpenFile(filename, unix.O_WRONLY|unix.O_CREAT|unix.O_EXCL, perm)
	if err != nil {
		return err
	}
	defer f.Close()
	_, err = f.Write(content)
	return err
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants