Hi, I’m also running into this issue and ran into a bit of a wall in attempting to find a workaround. Some additional notes:
Python scripts correctly prompt for passwords when invoking sudo
using subprocess
, despite it also using posix_spawn
.
python -c 'import subprocess; subprocess.run(["sudo", "echo", "foo"])'
Rust binaries can invoke sudo
using Command
, which also uses posix_spawn
.
cat ./cmd.rs
use std::error::Error;
use std::io::Write;
use std::process::Command;
use std::{io, process};
type Result<T> = ::std::result::Result<T, Box<dyn Error>>;
fn main() {
try_main().unwrap_or_else(|err| {
eprintln!("{}", err);
process::exit(1)
});
}
fn try_main() -> Result<()> {
let output = Command::new("/bin/sh")
.arg("-c")
.arg("sudo echo foo")
.output()
.map_err(|err| format!("Failed to invoke shell command. {}", err))?;
io::stdout().write_all(&output.stdout)?;
io::stderr().write_all(&output.stderr)?;
if output.status.success() {
Ok(())
} else {
match output.status.code() {
Some(code) => process::exit(code),
None => Err("Process terminated by signal".into()),
}
}
}
# Correctly prompts for password using sudo.
rustc ./cmd.rs && ./cmd
Calling posix_spawn
directly from Swift also seems to work without this issue:
import Foundation
func withCStrings(_ strings: [String], scoped: ([UnsafeMutablePointer<CChar>?]) throws -> Void) rethrows {
let cStrings = strings.map { strdup($0) }
try scoped(cStrings + [nil])
cStrings.forEach { free($0) }
}
enum RunCommandError: Error {
case WaitPIDError
case POSIXSpawnError(Int32)
}
func runCommand(_ command: String, completion: ((Int32) -> Void)? = nil) throws {
var pid: pid_t = 0
let args = ["sh", "-c", command]
let envs = ProcessInfo().environment.map { k, v in "\(k)=\(v)" }
try withCStrings(args) { cArgs in
try withCStrings(envs) { cEnvs in
var status = posix_spawn(&pid, "/bin/sh", nil, nil, cArgs, cEnvs)
if status == 0 {
if (waitpid(pid, &status, 0) != -1) {
completion?(status)
} else {
throw RunCommandError.WaitPIDError
}
} else {
throw RunCommandError.POSIXSpawnError(status)
}
}
}
}
// Correctly prompts for password using sudo.
// From https://gist.github.com/dduan/d4e967f3fc2801d3736b726cd34446bc
try runCommand("sudo echo foo")
// Hangs without prompting until exiting via CTRL-C (and doesn't accept input).
let process = Process()
process.launchPath = "/bin/sh"
process.arguments = ["-c", "sudo echo foo"]
process.launch()
process.waitUntilExit()
As pointed out in the previous reply, this looks similar to what’s being done internally via _CFPosixSpawn
.
In an attempt to diagnose the issue further, I tried building the SwiftFoundation
framework manually. Oddly enough, just linking the manually built framework directly fixes the issue with no other changes. This happens with either master
or swift-5.2.4-RELEASE
checked out, in either the debug or release configurations.
git clone https://github.com/apple/swift-corelibs-foundation.git
git switch --detach swift-5.2.4-RELEASE
xcodebuild -project ./Foundation.xcodeproj -derivedDataPath ./build -scheme SwiftFoundation -configuration Debug # (or release)
cd ../example
cat ./Package.swift
// swift-tools-version:5.2
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(name: "example")
package.platforms = [
.macOS(.v10_13),
]
package.products = [
.executable(name: "example", targets: ["main"]),
]
package.dependencies = []
package.targets = [
.target(
name: "main",
dependencies: []
),
]
cat ./Sources/main/main.swift
import SwiftFoundation
let process = Process()
process.launchPath = "/bin/sh"
process.arguments = ["-c", "sudo echo foo"]
process.launch()
process.waitUntilExit()
swift build -Xswiftc -F../swift-corelibs-foundation/build/Build/Products/Debug -c debug # (or release)
install_name_tool -add_rpath ../swift-corelibs-foundation/build/Build/Products/Debug ./.build/debug/example
# Correctly prompts for password using sudo.
./.build/debug/example
Anyone have more perspective on what’s going on here?