Repository navigation
fix(launchd): 本机 npm test 会打死外挂——沙箱调用不得操作真实自启作业 #10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
773134d
6dc5268
d80fae8
4da8eb2
f951da1
91c834a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -111,10 +111,75 @@ function uid() { | |||||||||||||||||||||||||||||||||||||
| try { return typeof process.getuid === 'function' ? process.getuid() : null; } catch (_) { return null; } | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| // launchctl 固定在 /bin/launchctl:这是 macOS 系统自带、不随版本漂移的绝对路径, | ||||||||||||||||||||||||||||||||||||||
| // 刻意不走 PATH 查找——PATH 上的同名文件可能被用户环境劫持,而这里的调用会 | ||||||||||||||||||||||||||||||||||||||
| // bootout 用户真实的自启作业。 | ||||||||||||||||||||||||||||||||||||||
| function launchctl(args) { | ||||||||||||||||||||||||||||||||||||||
| return execFileSync('/bin/launchctl', args, { stdio: 'pipe', timeout: 20000, encoding: 'utf8' }); | ||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
117
to
119
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggestion:
Suggested change
|
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| // 用户真实自启作业的 plist 路径——**绕过一切覆盖**(dir 参数 / ZCODE_ADVISOR_LAUNCHD_DIR)。 | ||||||||||||||||||||||||||||||||||||||
| // 完整安全不变量见 canTouchLaunchd 上方注释;这里只负责算出路径。 | ||||||||||||||||||||||||||||||||||||||
| function realPlistPath() { | ||||||||||||||||||||||||||||||||||||||
| return path.join(os.homedir(), 'Library', 'LaunchAgents', `${LABEL}.plist`); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| // 路径规范化:优先 realpath 父目录(plist 可能还没落盘,但父目录一定存在), | ||||||||||||||||||||||||||||||||||||||
| // 再退回 realpath 自身,最后退回 path.resolve。 | ||||||||||||||||||||||||||||||||||||||
| // | ||||||||||||||||||||||||||||||||||||||
| // 只做 path.resolve 会把「指向真实目录的符号链接」判成另一个位置:macOS 上 | ||||||||||||||||||||||||||||||||||||||
| // /var → /private/var、/tmp → /private/tmp 都是这种链接。后果是真实卸载被误判成 | ||||||||||||||||||||||||||||||||||||||
| // 「不是真实作业」而静默跳过——卸载功能本身失效。realpath 之后两个方向都不会误判: | ||||||||||||||||||||||||||||||||||||||
| // 指向真实目录的链接被认成真实(该 bootout),沙箱仍被认成沙箱(不碰 launchd 域)。 | ||||||||||||||||||||||||||||||||||||||
| // | ||||||||||||||||||||||||||||||||||||||
| // realpathSync 可能因目录在两次调用之间被删而抛(TOCTOU)。这里不回退重试而是直接 | ||||||||||||||||||||||||||||||||||||||
| // 落到 path.resolve:**兜底方向是安全的**——沙箱目录消失时判定结果仍是「不是真实作业」, | ||||||||||||||||||||||||||||||||||||||
| // 于是不碰 launchd 域(宁可不操作,也不能误打用户真实作业)。反之若真实目录消失, | ||||||||||||||||||||||||||||||||||||||
| // 两条路径都退化成 path.resolve,仍然相等,卸载照常生效。 | ||||||||||||||||||||||||||||||||||||||
| function canonicalPath(p) { | ||||||||||||||||||||||||||||||||||||||
| const dir = path.dirname(p); | ||||||||||||||||||||||||||||||||||||||
| const base = path.basename(p); | ||||||||||||||||||||||||||||||||||||||
| try { return path.join(fs.realpathSync(dir), base); } catch (_) { /* 父目录不存在 */ } | ||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||||||||||||||||||||||||||||||||||
| try { return fs.realpathSync(p); } catch (_) { /* 文件也不存在 */ } | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+142
to
+143
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggestion:
Suggested change
|
||||||||||||||||||||||||||||||||||||||
| return path.resolve(p); | ||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Comment on lines
+143
to
+144
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggestion:
Suggested change
|
||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+139
to
+145
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| // 目标 plist 是否就是用户真实那份(只有这份才允许操作 launchd 域)。 | ||||||||||||||||||||||||||||||||||||||
| // | ||||||||||||||||||||||||||||||||||||||
| // 注意:os.homedir() 在个别环境下会抛(用户记录查不到)。这里**不静默吞掉**: | ||||||||||||||||||||||||||||||||||||||
| // 吞掉会让 install 照旧把 plist 落盘、却永远不 bootstrap——正好是本 bug 的另一种形态 | ||||||||||||||||||||||||||||||||||||||
| // (文件在、作业不在、用户以为装好了)。出错时明确写 stderr 再返回 false。 | ||||||||||||||||||||||||||||||||||||||
| function managesRealAgent(p) { | ||||||||||||||||||||||||||||||||||||||
| let real; | ||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||
| real = realPlistPath(); | ||||||||||||||||||||||||||||||||||||||
| } catch (err) { | ||||||||||||||||||||||||||||||||||||||
| process.stderr.write(`[launchd] 无法定位用户 LaunchAgents 目录,跳过 launchd 域操作:${(err && err.message) || err}\n`); | ||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||||||||||||||||||||||||||||||||||
| return false; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+153
to
+159
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggestion:
Suggested change
|
||||||||||||||||||||||||||||||||||||||
| return canonicalPath(p) === canonicalPath(real); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| // launchd 域操作仅在 macOS、拿得到 uid、且**目标就是用户真实那份 plist** 时才允许。 | ||||||||||||||||||||||||||||||||||||||
| // | ||||||||||||||||||||||||||||||||||||||
| // 关键不变量:LABEL 是固定常量,重定向只能改 plist 的**路径**,改不掉 launchd | ||||||||||||||||||||||||||||||||||||||
| // 域里的作业名。所以一旦目标 plist 不是这一份,任何 | ||||||||||||||||||||||||||||||||||||||
| // `launchctl print/bootout gui/<uid>/<LABEL>` 都打在用户**真实**的自启作业上: | ||||||||||||||||||||||||||||||||||||||
| // - bootout 会终止真实作业的进程并把它从 launchd 卸载。KeepAlive 监督随作业 | ||||||||||||||||||||||||||||||||||||||
| // 一起消失 → 进程被杀且**永远不会自动重启**,直到用户重开一次 App | ||||||||||||||||||||||||||||||||||||||
| // (真机故障:在本机跑一次 `npm test`,外挂就被打死,表现为「动不动就连不上 | ||||||||||||||||||||||||||||||||||||||
| // controller」,而 CI 跑在 Linux 上、launchctl 不存在,ENOENT 被 catch 吞掉, | ||||||||||||||||||||||||||||||||||||||
| // 所以结构性看不见)。 | ||||||||||||||||||||||||||||||||||||||
| // - print 会把真实作业的运行态误报成沙箱的态(装了 agent 的开发机上, | ||||||||||||||||||||||||||||||||||||||
| // 「已写入但未加载」这条断言必然变红)。 | ||||||||||||||||||||||||||||||||||||||
| // 用路径判定而不是「有没有传 dir」:这样显式把 dir 指向真实目录、或经环境变量 | ||||||||||||||||||||||||||||||||||||||
| // 重定向的情形都能被正确识别。 | ||||||||||||||||||||||||||||||||||||||
| // 显式判断 platform/uid 而不是靠 execFileSync 抛 ENOENT 再吞掉——「静默吞掉」 | ||||||||||||||||||||||||||||||||||||||
| // 正是本 bug 在 CI 上无法暴露的原因。 | ||||||||||||||||||||||||||||||||||||||
| function canTouchLaunchd(p) { | ||||||||||||||||||||||||||||||||||||||
| return process.platform === 'darwin' && uid() !== null && managesRealAgent(p); | ||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+179
to
+181
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| // 写 plist(幂等:内容相同则不重写,保持 mtime 稳定,避免触发无谓的重载)。 | ||||||||||||||||||||||||||||||||||||||
| function writePlist(content, dir) { | ||||||||||||||||||||||||||||||||||||||
| const p = plistPath(dir); | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -141,9 +206,24 @@ function isLoaded(label) { | |||||||||||||||||||||||||||||||||||||
| // 幂等加载:已加载且 plist 未变 → 什么都不做(**不能无条件 bootout+bootstrap**, | ||||||||||||||||||||||||||||||||||||||
| // 那会把正在跑的实例杀掉再拉起,用户每次点图标都会闪断一次)。 | ||||||||||||||||||||||||||||||||||||||
| // plist 变了 → 必须重载,否则 launchd 仍按旧配置跑(升级后不生效)。 | ||||||||||||||||||||||||||||||||||||||
| // | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
206
to
+209
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggestion:
Suggested change
|
||||||||||||||||||||||||||||||||||||||
| // 注意 plistFile 指向沙箱时**绝不能**走 launchd 域(见 canTouchLaunchd): | ||||||||||||||||||||||||||||||||||||||
| // 那里的 bootout 打的是真实 LABEL,会把用户真实的 agent 打死并卸载。 | ||||||||||||||||||||||||||||||||||||||
| // | ||||||||||||||||||||||||||||||||||||||
| // 返回契约(三种形状,靠 skipped / warn 区分「跳过」与「失败」): | ||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||||||||||||||||||||||||||||||||||
| // { loaded:true, reloaded:boolean } —— 已加载(reloaded 表示是否重载过) | ||||||||||||||||||||||||||||||||||||||
| // { loaded:false, skipped:true, warn } —— 故意跳过域操作(沙箱/非 darwin/无 uid) | ||||||||||||||||||||||||||||||||||||||
| // { loaded:false, warn } —— 真失败(bootstrap 抛错) | ||||||||||||||||||||||||||||||||||||||
| // | ||||||||||||||||||||||||||||||||||||||
| // 注意 skipped 只表示「**没去碰** launchd 域」,与「碰了但作业没加载」是两回事; | ||||||||||||||||||||||||||||||||||||||
| // install/uninstall 的返回值沿用同一含义,调用方据此决定是否自行拉起进程。 | ||||||||||||||||||||||||||||||||||||||
| function ensureLoaded(plistFile, changed, label) { | ||||||||||||||||||||||||||||||||||||||
| const l = label || LABEL; | ||||||||||||||||||||||||||||||||||||||
| if (uid() === null) return { loaded: false, warn: '无法获取 uid,跳过加载' }; | ||||||||||||||||||||||||||||||||||||||
| if (uid() === null) return { loaded: false, skipped: true, warn: '无法获取 uid,跳过加载' }; | ||||||||||||||||||||||||||||||||||||||
| if (!canTouchLaunchd(plistFile)) { | ||||||||||||||||||||||||||||||||||||||
| // 只落盘,由调用方决定是否需要别的加载方式;不谎报「已加载」。 | ||||||||||||||||||||||||||||||||||||||
| return { loaded: false, skipped: true, warn: '目标不是用户真实 LaunchAgent,跳过 launchd 加载(避免影响真实自启作业)' }; | ||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+223
to
+226
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggestion:
Suggested change
|
||||||||||||||||||||||||||||||||||||||
| const loaded = isLoaded(l); | ||||||||||||||||||||||||||||||||||||||
| if (loaded && !changed) return { loaded: true, reloaded: false }; | ||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -155,6 +235,13 @@ function ensureLoaded(plistFile, changed, label) { | |||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| // 返回契约:{ ok, path, changed, loaded, skipped, warn?, reason? } | ||||||||||||||||||||||||||||||||||||||
| // ok:false + reason —— 未安装(找不到 controller / 工作树布局 / 写盘失败) | ||||||||||||||||||||||||||||||||||||||
| // ok:true + skipped:true —— 只落盘、**没去碰** launchd 域(沙箱/非 darwin/无 uid) | ||||||||||||||||||||||||||||||||||||||
| // ok:true + loaded:true —— 已交给 launchd 加载(changed 表示这次是否重写了 plist) | ||||||||||||||||||||||||||||||||||||||
| // ok:true + loaded:false + warn —— 碰了域但 bootstrap 失败 | ||||||||||||||||||||||||||||||||||||||
| // skipped=true 时 warn 说明跳过的原因(两者会同时出现);loaded=true 时才代表真的 | ||||||||||||||||||||||||||||||||||||||
| // 交给了 launchd。调用方据此决定是否自行拉起进程。 | ||||||||||||||||||||||||||||||||||||||
| function install(opts) { | ||||||||||||||||||||||||||||||||||||||
| const o = opts || {}; | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
245
to
246
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||||||||||||||||||||||||||||||||||
| const rt = o.nodeBin && o.controllerPath | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -182,43 +269,65 @@ function install(opts) { | |||||||||||||||||||||||||||||||||||||
| // --now 才立即 bootstrap(launchctl bootstrap 会因 RunAtLoad 立刻拉起 controller)。 | ||||||||||||||||||||||||||||||||||||||
| let loaded = false; | ||||||||||||||||||||||||||||||||||||||
| let warn = ''; | ||||||||||||||||||||||||||||||||||||||
| let skipped = false; | ||||||||||||||||||||||||||||||||||||||
| if (o.now) { | ||||||||||||||||||||||||||||||||||||||
| const r = ensureLoaded(written.path, written.changed); | ||||||||||||||||||||||||||||||||||||||
| loaded = r.loaded; | ||||||||||||||||||||||||||||||||||||||
| warn = r.warn || ''; | ||||||||||||||||||||||||||||||||||||||
| skipped = r.skipped === true; | ||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||||||||||||||||||||||||||||||||||
| if (!r.loaded && warn) { | ||||||||||||||||||||||||||||||||||||||
| return { ok: true, path: written.path, changed: written.changed, loaded: false, warn }; | ||||||||||||||||||||||||||||||||||||||
| return { ok: true, path: written.path, changed: written.changed, loaded: false, skipped, warn }; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| return { ok: true, path: written.path, changed: written.changed, loaded }; | ||||||||||||||||||||||||||||||||||||||
| return { ok: true, path: written.path, changed: written.changed, loaded, skipped }; | ||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| function uninstall(opts) { | ||||||||||||||||||||||||||||||||||||||
| const o = opts || {}; | ||||||||||||||||||||||||||||||||||||||
| const p = plistPath(o.dir); | ||||||||||||||||||||||||||||||||||||||
| // 只在「目标就是用户真实那份 + macOS」时才 bootout:否则会打掉用户真实的自启作业 | ||||||||||||||||||||||||||||||||||||||
| // (见 canTouchLaunchd)。重定向调用方(测试)只清理自己沙箱里的 plist 文件。 | ||||||||||||||||||||||||||||||||||||||
| // 返回 { ok, path, removed, bootedOut, skipped }:skipped=true 表示**没去碰** launchd | ||||||||||||||||||||||||||||||||||||||
| // 域(沙箱/非 darwin/无 uid),此时 bootedOut 恒为 false,且与「bootout 试过但作业 | ||||||||||||||||||||||||||||||||||||||
| // 本就没加载」区分开。 | ||||||||||||||||||||||||||||||||||||||
| let bootedOut = false; | ||||||||||||||||||||||||||||||||||||||
| if (uid() !== null) { | ||||||||||||||||||||||||||||||||||||||
| let skipped = false; | ||||||||||||||||||||||||||||||||||||||
| if (canTouchLaunchd(p)) { | ||||||||||||||||||||||||||||||||||||||
| try { launchctl(['bootout', `gui/${uid()}/${LABEL}`]); bootedOut = true; } catch (_) { /* 未加载:忽略 */ } | ||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||
| // 非 darwin / 拿不到 uid / 目标是沙箱:bootedOut=false 是**故意**的, | ||||||||||||||||||||||||||||||||||||||
| // 用 skipped 与「bootout 试过但作业本就没加载」区分开。 | ||||||||||||||||||||||||||||||||||||||
| skipped = true; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| let removed = false; | ||||||||||||||||||||||||||||||||||||||
| try { fs.unlinkSync(p); removed = true; } catch (_) { /* 不存在:忽略 */ } | ||||||||||||||||||||||||||||||||||||||
| return { ok: true, path: p, removed, bootedOut }; | ||||||||||||||||||||||||||||||||||||||
| return { ok: true, path: p, removed, bootedOut, skipped }; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| // 返回:{ path, exists, loaded, queried, state }。 | ||||||||||||||||||||||||||||||||||||||
| // loaded 的含义**始终**是「launchd 报告该作业已加载」;重定向(沙箱)时不去查询, | ||||||||||||||||||||||||||||||||||||||
| // 于是 loaded 恒为 false,靠 queried=false 表示「没查过」而不是「查过、没加载」。 | ||||||||||||||||||||||||||||||||||||||
| // 调用方若要区分这两种情况,看 queried。 | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+307
to
+310
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||||||||||||||||||||||||||||||||||
| function status(opts) { | ||||||||||||||||||||||||||||||||||||||
| const o = opts || {}; | ||||||||||||||||||||||||||||||||||||||
| const p = plistPath(o.dir); | ||||||||||||||||||||||||||||||||||||||
| const exists = fs.existsSync(p); | ||||||||||||||||||||||||||||||||||||||
| // 目标不是真实那份时不去查 launchd 域:作业名仍是真实 LABEL,查出来的是用户真实 | ||||||||||||||||||||||||||||||||||||||
| // agent 的态,当成沙箱 plist 的态返回会让调用方拿到自相矛盾的结果(沙箱里文件不存在, | ||||||||||||||||||||||||||||||||||||||
| // 却报 loaded=true)——装了 agent 的开发机上这条断言必红。 | ||||||||||||||||||||||||||||||||||||||
| // 返回 loaded=false + queried=false,让「没查过」与「查过、没加载」可区分。 | ||||||||||||||||||||||||||||||||||||||
| let loaded = false; | ||||||||||||||||||||||||||||||||||||||
| let queried = false; | ||||||||||||||||||||||||||||||||||||||
| let detail = ''; | ||||||||||||||||||||||||||||||||||||||
| if (uid() !== null) { | ||||||||||||||||||||||||||||||||||||||
| if (canTouchLaunchd(p)) { | ||||||||||||||||||||||||||||||||||||||
| queried = true; | ||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Comment on lines
319
to
+323
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
建议明确初始化 let queried = false;
let loaded = false;
let detail = '';
if (canTouchLaunchd(p)) {
queried = true;
try {
detail = launchctl(['print', `gui/${uid()}/${LABEL}`]);
loaded = true;
} catch (_) { /* 未加载 */ }
}Suggestion:
Suggested change
|
||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||
| detail = launchctl(['print', `gui/${uid()}/${LABEL}`]); | ||||||||||||||||||||||||||||||||||||||
| loaded = true; | ||||||||||||||||||||||||||||||||||||||
| } catch (_) { /* 未加载 */ } | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| const state = /state = (\w+)/.exec(detail); | ||||||||||||||||||||||||||||||||||||||
| return { path: p, exists, loaded, state: state ? state[1] : '' }; | ||||||||||||||||||||||||||||||||||||||
| return { path: p, exists, loaded, queried, state: state ? state[1] : '' }; | ||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| function main(argv) { | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -231,11 +340,19 @@ function main(argv) { | |||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| if (cmd === 'uninstall') { | ||||||||||||||||||||||||||||||||||||||
| const r = uninstall(); | ||||||||||||||||||||||||||||||||||||||
| console.log(`[launchd] uninstall: ${r.removed ? '已移除' : '文件不存在'} ${r.path}`); | ||||||||||||||||||||||||||||||||||||||
| // skipped=true 时说明**没去碰** launchd 域(沙箱/非 darwin/无 uid),此时 | ||||||||||||||||||||||||||||||||||||||
| // 「已移除/文件不存在」只描述 plist 文件,不代表真实作业被卸载——打印出来, | ||||||||||||||||||||||||||||||||||||||
| // 免得看日志的人把「文件删了」当成「作业也停了」。 | ||||||||||||||||||||||||||||||||||||||
| const skip = r.skipped ? '(未操作 launchd 域)' : ''; | ||||||||||||||||||||||||||||||||||||||
| console.log(`[launchd] uninstall: ${r.removed ? '已移除' : '文件不存在'}${skip} ${r.path}`); | ||||||||||||||||||||||||||||||||||||||
| return 0; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| const s = status(); | ||||||||||||||||||||||||||||||||||||||
| console.log(`[launchd] status: plist=${s.exists ? '存在' : '不存在'} loaded=${s.loaded ? '是' : '否'}${s.state ? ` state=${s.state}` : ''} path=${s.path}`); | ||||||||||||||||||||||||||||||||||||||
| // queried=false 表示**没查** launchd 域(沙箱/非 darwin/无 uid),与「查过、没加载」 | ||||||||||||||||||||||||||||||||||||||
| // 是两回事。只打 loaded=否 会让调用方(packagers/install-templates 里的 | ||||||||||||||||||||||||||||||||||||||
| // `grep -q 'loaded=是'`)把「没查过」误读成「作业没在跑」。 | ||||||||||||||||||||||||||||||||||||||
| const q = s.queried === false ? ' queried=否' : ''; | ||||||||||||||||||||||||||||||||||||||
| console.log(`[launchd] status: plist=${s.exists ? '存在' : '不存在'} loaded=${s.loaded ? '是' : '否'}${q}${s.state ? ` state=${s.state}` : ''} path=${s.path}`); | ||||||||||||||||||||||||||||||||||||||
| return 0; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
在代码中多处使用了非标准拼音(如
bootout),这可能影响国际化维护。建议统一使用英文术语(如bootout可以替换为unload或保持英文bootout),以提高代码的可读性和一致性。