Skip to content

Commit 4f49068

Browse files
committed
Refactor to use pipe, dup2, fork, execvp
1 parent 851f89b commit 4f49068

File tree

1 file changed

+57
-31
lines changed

1 file changed

+57
-31
lines changed

aws-runtime/aws-config/native/src/aws/sdk/kotlin/runtime/auth/credentials/executeCommandNative.kt

Lines changed: 57 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,9 @@ import aws.smithy.kotlin.runtime.io.internal.SdkDispatchers
99
import aws.smithy.kotlin.runtime.time.Clock
1010
import aws.smithy.kotlin.runtime.util.OsFamily
1111
import aws.smithy.kotlin.runtime.util.PlatformProvider
12-
import kotlinx.cinterop.ExperimentalForeignApi
13-
import kotlinx.cinterop.refTo
14-
import kotlinx.cinterop.toKString
1512
import kotlinx.coroutines.withContext
1613
import kotlinx.coroutines.withTimeout
14+
import kotlinx.cinterop.*
1715
import 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

Comments
 (0)