diff --git a/Sources/removemacai/SelfTest.swift b/Sources/removemacai/SelfTest.swift index e260482..6db65ec 100644 --- a/Sources/removemacai/SelfTest.swift +++ b/Sources/removemacai/SelfTest.swift @@ -217,6 +217,12 @@ func selfTest() -> Bool { program: "/Library/PrivilegedHelperTools/dev.orbstack.OrbStack.privhelper", system: true) check(script.owner == "sync.sh" && helper.owner == "OrbStack", "background items are named after what they run") + let ranAll = Shell.run("/bin/sh", ["-c", Shell.adminScript(["false", "echo second"])]) + check(!ranAll.ok && ranAll.output.contains("second"), "one failed administrator step doesn't skip the rest") + let exited = Shell.run("/bin/sh", ["-c", Shell.adminScript(["exit 3", "echo after-exit"])]) + check(!exited.ok && exited.output.contains("after-exit"), "a step that exits doesn't stop the rest") + check(Shell.run("/bin/sh", ["-c", Shell.adminScript(["true", "true"])]).ok, "administrator steps succeed together") + print(failed == 0 ? Term.green("all checks passed") : Term.red("\(failed) failed")) return failed == 0 } diff --git a/Sources/removemacai/Shell.swift b/Sources/removemacai/Shell.swift index 7f243d7..0f2895a 100644 --- a/Sources/removemacai/Shell.swift +++ b/Sources/removemacai/Shell.swift @@ -26,10 +26,11 @@ enum Shell { static func quote(_ s: String) -> String { "'" + s.replacingOccurrences(of: "'", with: "'\\''") + "'" } /// Runs shell commands as root after one macOS password prompt. From a - /// terminal, sudo asks instead. + /// terminal, sudo asks instead. Every command runs even when an earlier one + /// fails; the result fails if any of them did. static func admin(_ commands: [String], prompt: String) -> Result { guard !commands.isEmpty else { return Result(status: 0, output: "") } - let script = commands.joined(separator: " && ") + let script = adminScript(commands) if getuid() == 0 { return run("/bin/sh", ["-c", script]) } if isatty(STDIN_FILENO) == 1 && !Shell.inApp { return run("/usr/bin/sudo", ["/bin/sh", "-c", script]) @@ -41,6 +42,10 @@ enum Shell { ]) } + static func adminScript(_ commands: [String]) -> String { + (["rc=0"] + commands.map { "( \($0) ) || rc=1" } + ["exit $rc"]).joined(separator: "; ") + } + /// A readable reason for a failed administrator step. osascript prefixes /// "0:390: execution error:" and appends the error number. static func adminError(_ result: Result) -> String {