@@ -9,11 +9,9 @@ import aws.smithy.kotlin.runtime.io.internal.SdkDispatchers
99import aws.smithy.kotlin.runtime.time.Clock
1010import aws.smithy.kotlin.runtime.util.OsFamily
1111import aws.smithy.kotlin.runtime.util.PlatformProvider
12- import kotlinx.cinterop.ExperimentalForeignApi
13- import kotlinx.cinterop.refTo
14- import kotlinx.cinterop.toKString
1512import kotlinx.coroutines.withContext
1613import kotlinx.coroutines.withTimeout
14+ import kotlinx.cinterop.*
1715import platform.posix.*
1816
1917@OptIn(ExperimentalForeignApi ::class )
@@ -23,44 +21,72 @@ internal actual suspend fun executeCommand(
2321 maxOutputLengthBytes : Long ,
2422 timeoutMillis : Long ,
2523 clock : Clock ,
26- ): Pair <Int , String > {
27- // add the platform's shell
28- val prefix = when (platformProvider.osInfo().family) {
29- OsFamily .Windows -> " cmd.exe /C"
30- else -> " sh -c"
24+ ): Pair <Int , String > = withContext(SdkDispatchers .IO ) {
25+ val pipeFd = IntArray (2 )
26+ if (pipe(pipeFd.refTo(0 )) != 0 ) {
27+ error(" Failed to create pipe" )
3128 }
3229
33- val commandToExecute = " $prefix \" $command \" 2>&1"
30+ val pid = fork()
31+ if (pid == - 1 ) {
32+ error(" Failed to fork" )
33+ }
34+
35+ if (pid == 0 ) {
36+ // Child process
37+ close(pipeFd[0 ]) // Close read end
38+
39+ // Pass stdout and stderr back to parent
40+ dup2(pipeFd[1 ], STDOUT_FILENO )
41+ dup2(pipeFd[1 ], STDERR_FILENO )
42+ close(pipeFd[1 ])
43+
44+ val shell = when (platformProvider.osInfo().family) {
45+ OsFamily .Windows -> " cmd.exe"
46+ else -> " sh"
47+ }
48+
49+ val shellArg = when (platformProvider.osInfo().family) {
50+ OsFamily .Windows -> " /C"
51+ else -> " -c"
52+ }
3453
35- return withContext(SdkDispatchers .IO ) {
36- val fp = popen(commandToExecute, " r" ) ? : error(" Failed to execute popen: $commandToExecute " )
54+ val argv = memScoped { (arrayOf(shell, shellArg, command).map { it.cstr.ptr } + null ).toCValues() }
55+ execvp(shell, argv)
56+ _exit (127 ) // If exec fails
57+ }
58+
59+ // Parent process
60+ close(pipeFd[1 ]) // Close write end
3761
38- try {
39- val output = buildString {
40- val buffer = ByteArray (maxOutputLengthBytes.toInt())
62+ val output = buildString {
63+ val buffer = ByteArray (maxOutputLengthBytes.toInt())
4164
42- withTimeout(timeoutMillis) {
43- while (true ) {
44- val input = fgets( buffer.refTo(0 ), buffer.size, fp) ? : break
45- append(input.toKString())
65+ withTimeout(timeoutMillis) {
66+ while (true ) {
67+ val bytesRead = read(pipeFd[ 0 ], buffer.refTo(0 ), buffer.size.toULong()).toInt()
68+ if (bytesRead <= 0 ) break
4669
47- if (length > maxOutputLengthBytes) {
48- throw CredentialsProviderException (" Process output exceeded limit of $maxOutputLengthBytes bytes" )
49- }
50- }
70+ append(buffer.decodeToString(0 , bytesRead))
71+
72+ if (length > maxOutputLengthBytes) {
73+ close(pipeFd[0 ])
74+ throw CredentialsProviderException (" Process output exceeded limit of $maxOutputLengthBytes bytes" )
5175 }
5276 }
77+ }
78+ }
5379
54- val status = pclose(fp)
55- val exitCode = when (platformProvider.osInfo().family) {
56- OsFamily .Windows -> status // Windows returns the exit code directly
57- else -> (status shr 8 ) and 0xFF // Posix systems need to use the WEXITSTATUS macro, this is equivalent
58- }
80+ close(pipeFd[0 ])
5981
60- exitCode to output
61- } catch (e: Exception ) {
62- pclose(fp)
63- throw e
82+ memScoped {
83+ val status = alloc<IntVar >()
84+ waitpid(pid, status.ptr, 0 )
85+ val exitCode = when (platformProvider.osInfo().family) {
86+ OsFamily .Windows -> status.value
87+ else -> (status.value shr 8 ) and 0xFF
6488 }
89+
90+ exitCode to output
6591 }
6692}
0 commit comments