Skip to content

fix: prep-cmd permission_denied 例外未覆盖 wait/exit_code 检查#678

Closed
Han-cy830 wants to merge 3 commits into
AlkaidLab:masterfrom
Han-cy830:fix/prep-cmd-permission-denied-skip
Closed

fix: prep-cmd permission_denied 例外未覆盖 wait/exit_code 检查#678
Han-cy830 wants to merge 3 commits into
AlkaidLab:masterfrom
Han-cy830:fix/prep-cmd-permission-denied-skip

Conversation

@Han-cy830
Copy link
Copy Markdown

问题

在桌面模式(_app.cmd 为空)下,当 Windows 会话锁定时,Session 0 中的进程创建会返回 permission_denied

execute() 函数已有例外处理:当 run_command 返回 permission_denied 且为桌面模式时,跳过错误继续执行。但后续的 child.wait() 同样返回 permission_denied没有被例外覆盖,直接 return -1,触发 fail_guard 调用 terminate() 销毁 VDD,串流中断。

日志证据

[16:53:59.910]: Error: Couldn't run [.\tools\SetDpi.exe 175]: System: Permission denied
[16:53:59.910]: Error: [.\tools\SetDpi.exe 175] wait failed with error code [generic:13]
[16:53:59.910]: Error: [.\tools\SetDpi.exe 175] exited with code [-1]
[16:53:59.913]: Info: apply_config 未执行(无persistent_data),销毁VDD并跳过拓扑恢复

第一层 run_command 失败被例外跳过,但第二层 child.wait() 失败未被跳过。

修复

添加 skip_prep_failure 标志,当命中桌面模式 + permission_denied 例外时,同时跳过 wait()exit_code() 检查,prep-cmd 被记录为失败但不阻止串流继续。

效果

  • 锁屏时:prep-cmd 失败被跳过,串流正常启动
  • 解锁时:prep-cmd 正常执行(如 DPI 切换)
  • 不需要清空 apps.json 中的 prep-cmd 配置

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 25, 2026

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b8c31c13-042d-44b0-842c-1e5af6b65040

📥 Commits

Reviewing files that changed from the base of the PR and between 8b452cc and 50fe185.

📒 Files selected for processing (1)
  • .github/workflows/main.yml
📜 Recent review details
🔇 Additional comments (1)
.github/workflows/main.yml (1)

164-164: LGTM!

Also applies to: 201-201, 248-248, 251-251


Summary by CodeRabbit

  • Bug Fixes
    • 改进了桌面模式下对预启动命令遇到的特定权限被拒绝情形的处理:在该情况下应用可继续运行而非立即失败,提升稳定性与用户体验。
  • Chores
    • 调整了 Windows 构建触发条件,以优化手动触发与来自 fork 的构建行为。

Walkthrough

对预启动命令错误处理加入 skip_prep_failure 条件以在桌面模式遇到权限拒绝时跳过等待与退出码检查;并将 CI 工作流中 DRIVER_DEPS_REQUIRED 的禁用条件扩展到 workflow_dispatch 触发。

变更内容

预启动命令与 CI 条件更改

Layer / File(s) Summary
桌面模式权限错误处理
src/process.cpp
添加 skip_prep_failure:当 platf::run_command 返回 permission_denied_app.cmd.empty()(桌面模式)时,跳过对 childwait(ec) 与退出码校验。
CI: DRIVER_DEPS_REQUIRED 触发条件更新
.github/workflows/main.yml
DRIVER_DEPS_REQUIRED 的禁用条件从仅针对 fork 的 pull_request 扩展为同时包含 workflow_dispatch,其它触发类型保持为启用;并对 Inno Setup 调用前后空行做小幅排版调整。

🎯 3 (Moderate) | ⏱️ ~20 分钟

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed 标题准确总结了主要变更:在桌面模式下对 permission_denied 异常处理时,扩展覆盖 wait/exit_code 检查的逻辑。
Description check ✅ Passed 描述清晰地关联了变更:说明了 permission_denied 异常未覆盖 wait/exit_code 检查的问题、日志证据、修复方案及预期效果。
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/process.cpp`:
- Around line 246-272: The loop currently advances _app_prep_it even when a prep
cmd was skipped due to skip_prep_failure, causing terminate() to treat that prep
as executed and run its undo_cmd; modify the logic so only actually-successful
prep commands are recorded for undo: move iterator advancement and any
push-to-undo collection (or mark) into the success path after child.exit_code()
confirms success (i.e., only when do_cmd ran and returned 0), and do not add or
mark the current prep_cmd when skip_prep_failure is true; update the code paths
around skip_prep_failure, _app_prep_it, and any undo stack used by terminate()
so undo_cmd runs only for commands that truly executed successfully.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 05d695a1-4cf6-4060-9671-41a0bcd6145c

📥 Commits

Reviewing files that changed from the base of the PR and between a5d5852 and f2bbd5a.

📒 Files selected for processing (1)
  • src/process.cpp
📜 Review details
🧰 Additional context used
📓 Path-based instructions (1)
src/**/*.{cpp,c,h}

⚙️ CodeRabbit configuration file

src/**/*.{cpp,c,h}: Sunshine 核心 C++ 源码,自托管游戏串流服务器。审查要点:内存安全、 线程安全、RAII 资源管理、安全漏洞。注意预处理宏控制的平台相关代码。

Files:

  • src/process.cpp

Comment thread src/process.cpp
Comment on lines +246 to 272
// Track if we hit the desktop-mode permission_denied exception,
// so we can also skip the wait/exit_code checks that would otherwise fail.
bool skip_prep_failure = false;
if (ec) {
BOOST_LOG(error) << "Couldn't run ["sv << cmd.do_cmd << "]: System: "sv << ec.message();
// We don't want any prep commands failing launch of the desktop.
// This is to prevent the issue where users reboot their PC and need to log in with Sunshine.
// permission_denied is typically returned when the user impersonation fails, which can happen when user is not signed in yet.
if (!(_app.cmd.empty() && ec == std::errc::permission_denied)) {
if (_app.cmd.empty() && ec == std::errc::permission_denied) {
skip_prep_failure = true;
} else {
return -1;
}
}

child.wait(ec);
if (ec) {
BOOST_LOG(error) << '[' << cmd.do_cmd << "] wait failed with error code ["sv << ec << ']';
return -1;
}
auto ret = child.exit_code();
if (ret != 0) {
BOOST_LOG(error) << '[' << cmd.do_cmd << "] exited with code ["sv << ret << ']';
return -1;
if (!skip_prep_failure) {
child.wait(ec);
if (ec) {
BOOST_LOG(error) << '[' << cmd.do_cmd << "] wait failed with error code ["sv << ec << ']';
return -1;
}
auto ret = child.exit_code();
if (ret != 0) {
BOOST_LOG(error) << '[' << cmd.do_cmd << "] exited with code ["sv << ret << ']';
return -1;
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

跳过失败后会错误执行对应 undo_cmd,可能触发反向副作用。

在 Line 255 命中“桌面模式 + permission_denied”后,代码会继续并让循环推进 _app_prep_it。这会让 terminate() 把该 prep_cmd 视为已执行,从而运行它的 undo_cmd,即使 do_cmd 实际未成功执行。建议把“遍历游标”与“已成功执行边界”分离,只将成功执行的命令纳入 undo 范围。

修复方向(示意)
- for (; _app_prep_it != std::end(_app.prep_cmds); ++_app_prep_it) {
-   auto &cmd = *_app_prep_it;
+ auto scan_it = std::begin(_app.prep_cmds);
+ _app_prep_it = _app_prep_begin; // executed_end
+ for (; scan_it != std::end(_app.prep_cmds); ++scan_it) {
+   auto &cmd = *scan_it;
    ...
+   bool executed_ok = false;
    if (ec) {
      ...
      if (_app.cmd.empty() && ec == std::errc::permission_denied) {
        skip_prep_failure = true;
      } else {
        return -1;
      }
    }

    if (!skip_prep_failure) {
      child.wait(ec);
      ...
      if (ret != 0) {
        return -1;
      }
+     executed_ok = true;
    }
+
+   if (executed_ok) {
+     _app_prep_it = scan_it + 1; // 仅成功 do_cmd 才推进 undo 边界
+   }
  }

As per coding guidelines, “src/**/*.{cpp,c,h}: Sunshine 核心 C++ 源码,自托管游戏串流服务器。审查要点:内存安全、 线程安全、RAII 资源管理、安全漏洞。注意预处理宏控制的平台相关代码。”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/process.cpp` around lines 246 - 272, The loop currently advances
_app_prep_it even when a prep cmd was skipped due to skip_prep_failure, causing
terminate() to treat that prep as executed and run its undo_cmd; modify the
logic so only actually-successful prep commands are recorded for undo: move
iterator advancement and any push-to-undo collection (or mark) into the success
path after child.exit_code() confirms success (i.e., only when do_cmd ran and
returned 0), and do not add or mark the current prep_cmd when skip_prep_failure
is true; update the code paths around skip_prep_failure, _app_prep_it, and any
undo stack used by terminate() so undo_cmd runs only for commands that truly
executed successfully.

@Yundi339
Copy link
Copy Markdown
Member

请问你有测试过吗?

@Yundi339
Copy link
Copy Markdown
Member

我记得锁屏中本来就不能修改dpi这些屏幕设置,会被系统强制拦截。
而且其实我是反对锁屏中使用命令的,涉及安全问题,后面这个还要做个细致化的开关

@Han-cy830
Copy link
Copy Markdown
Author

感谢回复。关于这两点:

  1. DPI在锁屏时不生效 — 确实如此,ChangeDisplaySettingsExSetDpi.exe 在Session 0中都会失败。所以这个PR的实际效果是:锁屏时prep-cmd失败被跳过,串流继续启动(但DPI不会变);解锁时prep-cmd正常执行,DPI切换生效。

  2. 安全问题 — 理解。目前代码中已经存在 permission_denied 例外(桌面模式下跳过失败的prep-cmd),这个PR只是修复了该例外不完整导致的bug(child.wait() 返回同样的错误却没被跳过)。如果后续要做细粒度开关,可以考虑在 prep_cmd_t 中添加类似 skip-on-failureallow-locked 的配置字段,让用户自行控制哪些命令在锁屏时允许失败跳过。

如果这个PR的方向不对,可以关闭,等后续的细粒度开关方案一起处理。

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.github/workflows/main.yml (1)

197-201: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

注释未同步更新以说明 workflow_dispatch 的逻辑。

第 197-200 行的注释仅解释了 fork PR 无法访问私有仓库驱动下载所需的 secrets,故需将缺失驱动从致命错误降级为警告。但第 201 行新增的逻辑同时将 workflow_dispatch 手动触发的构建也设置为 OFF,而注释未提及此场景。

建议补充注释说明为何 workflow_dispatch 同样需要 DRIVER_DEPS_REQUIRED=OFF(例如:是否出于测试便利性考虑、或手动触发同样可能缺少某些驱动)。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/main.yml around lines 197 - 201, 注释未说明为什么条件还将
workflow_dispatch 设置为 OFF:更新注释关联到 DRIVER_DEPS_REQUIRED 的逻辑,明确说明该变量在 pull_request
来自 fork 或手动触发 (workflow_dispatch) 时会被置为 OFF,并解释为何对 workflow_dispatch
也采用相同降级策略(例如手动触发可能缺少 org-scoped secrets、便于本地/手动测试或避免在人工触发时失败);在注释中引用
github.event_name == 'workflow_dispatch' 和
github.event.pull_request.head.repo.fork 这两个条件以便读者能直接对应到 DRIVER_DEPS_REQUIRED
的判定逻辑。
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/main.yml:
- Line 201: The current DRIVER_DEPS_REQUIRED expression in main.yml treats
workflow_dispatch the same as forked PRs and can silently allow missing drivers;
change the condition so DRIVER_DEPS_REQUIRED is set to 'OFF' only for forked
pull requests (github.event_name == 'pull_request' &&
github.event.pull_request.head.repo.fork) and keep workflow_dispatch defaulting
to 'ON'; update the main.yml comment near DRIVER_DEPS_REQUIRED to explain that
only forked PRs cannot access secrets.DRIVER_DOWNLOAD_TOKEN and therefore must
tolerate missing drivers (or alternatively add a workflow_dispatch input to
explicitly allow non-fatal missing drivers if you prefer that behavior).

---

Outside diff comments:
In @.github/workflows/main.yml:
- Around line 197-201: 注释未说明为什么条件还将 workflow_dispatch 设置为 OFF:更新注释关联到
DRIVER_DEPS_REQUIRED 的逻辑,明确说明该变量在 pull_request 来自 fork 或手动触发 (workflow_dispatch)
时会被置为 OFF,并解释为何对 workflow_dispatch 也采用相同降级策略(例如手动触发可能缺少 org-scoped
secrets、便于本地/手动测试或避免在人工触发时失败);在注释中引用 github.event_name == 'workflow_dispatch' 和
github.event.pull_request.head.repo.fork 这两个条件以便读者能直接对应到 DRIVER_DEPS_REQUIRED
的判定逻辑。
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d76bbc2b-75c0-465c-8829-2073cd2717b1

📥 Commits

Reviewing files that changed from the base of the PR and between f2bbd5a and 8b452cc.

📒 Files selected for processing (1)
  • .github/workflows/main.yml

Comment thread .github/workflows/main.yml Outdated
@Yundi339
Copy link
Copy Markdown
Member

感谢回复。关于这两点:

  1. DPI在锁屏时不生效 — 确实如此,ChangeDisplaySettingsExSetDpi.exe 在Session 0中都会失败。所以这个PR的实际效果是:锁屏时prep-cmd失败被跳过,串流继续启动(但DPI不会变);解锁时prep-cmd正常执行,DPI切换生效。
  2. 安全问题 — 理解。目前代码中已经存在 permission_denied 例外(桌面模式下跳过失败的prep-cmd),这个PR只是修复了该例外不完整导致的bug(child.wait() 返回同样的错误却没被跳过)。如果后续要做细粒度开关,可以考虑在 prep_cmd_t 中添加类似 skip-on-failureallow-locked 的配置字段,让用户自行控制哪些命令在锁屏时允许失败跳过。

如果这个PR的方向不对,可以关闭,等后续的细粒度开关方案一起处理。

不要用ai回答我。

@Yundi339
Copy link
Copy Markdown
Member

关于你的这个场景,你的诉求场景是:串流后如果是处于锁屏状态,解锁屏幕再执行对应prep-cmd
这个场景我觉得挺合理的,但是实现上是挺复杂的,需要做个任务队列+事件监听,还要考虑锁之类的问题。

@Han-cy830
Copy link
Copy Markdown
Author

Han-cy830 commented May 25, 2026 via email

@Han-cy830
Copy link
Copy Markdown
Author

Han-cy830 commented May 25, 2026 via email

Per maintainer feedback, workflow changes are out of scope for this PR.
Only the process.cpp fix should be included.
@Han-cy830
Copy link
Copy Markdown
Author

Han-cy830 commented May 25, 2026 via email

@Han-cy830 Han-cy830 closed this May 25, 2026
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