@@ -33,12 +33,12 @@ import scala.scalanative.posix.sys.wait.*
3333import scala .scalanative .posix .errno .*
3434import scala .scalanative .meta .LinktimeInfo
3535import scala .scalanative .posix .unistd .*
36- import scala .scalanative .posix .spawn .*
3736import scala .scalanative .posix .signal .*
3837import java .io .IOException
3938import scala .concurrent .duration .*
4039import cats .effect .LiftIO
4140import cats .effect .IO
41+ import cats .effect .implicits .*
4242import org .typelevel .scalaccompat .annotation ._
4343
4444@ extern
@@ -57,32 +57,44 @@ object PidFd {
5757 }
5858}
5959
60+ final case class NativeProcess (
61+ pid : pid_t,
62+ stdinFd : Int ,
63+ stdoutFd : Int ,
64+ stderrFd : Int
65+ )
66+
6067private [process] trait ProcessesCompanionPlatform {
6168 def forAsync [F [_]: LiftIO ](implicit F : Async [F ]): Processes [F ] = new UnsealedProcesses [F ] {
6269
6370 def spawn (process : ProcessBuilder ): Resource [F , Process [F ]] = {
6471
6572 def createProcess (): F [NativeProcess ] = F .blocking {
6673 Zone { implicit z =>
67- val allArgs = process.command +: process.args
68- val argv = stackalloc[CString ](allArgs.length.toULong + 1 .toULong)
69- allArgs.zipWithIndex.foreach { case (arg, i) =>
70- argv(i.toULong) = toCString(arg)
74+ def findExecutable (command : String ): Option [String ] = {
75+ val pathEnv = sys.env.get(" PATH" ).getOrElse(" " )
76+ val paths = pathEnv.split(" :" ).toList
77+
78+ paths
79+ .find { dir =>
80+ val fullPath = s " $dir/ $command"
81+ access(toCString(fullPath), X_OK ) == 0
82+ }
83+ .map(dir => s " $dir/ $command" )
84+
7185 }
72- argv(allArgs.length.toULong) = null
7386
7487 val envMap =
7588 if (process.inheritEnv)
7689 sys.env ++ process.extraEnv
77- else
78- process.extraEnv
79-
80- val envp = stackalloc[CString ]((envMap.size + 1 ).toULong)
81- envMap.zipWithIndex.foreach { case ((k, v), i) =>
82- envp(i.toULong) = toCString(s " $k= $v" )
83- }
84- envp(envMap.size.toULong) = null
90+ else process.extraEnv
8591
92+ val executable =
93+ if (process.command.contains(" /" )) {
94+ process.command
95+ } else {
96+ findExecutable(process.command).getOrElse(process.command)
97+ }
8698 val stdinPipe = stackalloc[CInt ](2 )
8799 val stdoutPipe = stackalloc[CInt ](2 )
88100 val stderrPipe = stackalloc[CInt ](2 )
@@ -91,47 +103,62 @@ private[process] trait ProcessesCompanionPlatform {
91103 throw new RuntimeException (" Failed to create pipes" )
92104 }
93105
94- val fileActions = stackalloc[posix_spawn_file_actions_t]()
95- posix_spawn_file_actions_init(fileActions)
96-
97- posix_spawn_file_actions_adddup2(fileActions, stdinPipe(0 ), STDIN_FILENO )
98- posix_spawn_file_actions_addclose(fileActions, stdinPipe(1 ))
99-
100- posix_spawn_file_actions_adddup2(fileActions, stdoutPipe(1 ), STDOUT_FILENO )
101- posix_spawn_file_actions_addclose(fileActions, stdoutPipe(0 ))
102-
103- posix_spawn_file_actions_adddup2(fileActions, stderrPipe(1 ), STDERR_FILENO )
104- posix_spawn_file_actions_addclose(fileActions, stderrPipe(0 ))
105-
106- val pid = stackalloc[pid_t]()
107- val result = posix_spawnp(
108- pid,
109- argv(0 ),
110- fileActions,
111- null ,
112- argv,
113- envp
114- )
115-
116- posix_spawn_file_actions_destroy(fileActions)
117-
118- if (result != 0 ) {
106+ val pid = fork()
107+ if (pid < 0 ) {
119108 close(stdinPipe(0 )); close(stdinPipe(1 ))
120109 close(stdoutPipe(0 )); close(stdoutPipe(1 ))
121110 close(stderrPipe(0 )); close(stderrPipe(1 ))
122- throw new RuntimeException (s " posix_spawn failed: $result" )
123- }
111+ throw new RuntimeException (" fork failed" )
112+ } else if (pid == 0 ) {
113+ close(stdinPipe(1 ))
114+ close(stdoutPipe(0 ))
115+ close(stderrPipe(0 ))
116+
117+ if (
118+ dup2(stdinPipe(0 ), STDIN_FILENO ) == - 1 ||
119+ dup2(stdoutPipe(1 ), STDOUT_FILENO ) == - 1 ||
120+ dup2(stderrPipe(1 ), STDERR_FILENO ) == - 1
121+ ) {
122+ _exit(1 )
123+ }
124124
125- close(stdinPipe(0 ))
126- close(stdoutPipe(1 ))
127- close(stderrPipe(1 ))
125+ close(stdinPipe(0 ))
126+ close(stdoutPipe(1 ))
127+ close(stderrPipe(1 ))
128128
129- NativeProcess (
130- pid = ! pid,
131- stdinFd = stdinPipe(1 ),
132- stdoutFd = stdoutPipe(0 ),
133- stderrFd = stderrPipe(0 )
134- )
129+ process.workingDirectory.foreach { dir =>
130+ if (chdir(toCString(dir.toString)) != 0 ) {
131+ _exit(1 )
132+ }
133+ }
134+
135+ val allArgs = process.command +: process.args
136+ val argv = stackalloc[CString ](allArgs.length.toULong + 1 .toULong)
137+ allArgs.zipWithIndex.foreach { case (arg, i) =>
138+ argv(i.toULong) = toCString(arg)
139+ }
140+ argv(allArgs.length.toULong) = null
141+
142+ val envp = stackalloc[CString ]((envMap.size + 1 ).toULong)
143+ envMap.zipWithIndex.foreach { case ((k, v), i) =>
144+ envp(i.toULong) = toCString(s " $k= $v" )
145+ }
146+ envp(envMap.size.toULong) = null
147+
148+ execve(toCString(executable), argv, envp)
149+ _exit(1 )
150+ throw new RuntimeException (s " execve failed " )
151+ } else {
152+ close(stdinPipe(0 ))
153+ close(stdoutPipe(1 ))
154+ close(stderrPipe(1 ))
155+ NativeProcess (
156+ pid = pid,
157+ stdinFd = stdinPipe(1 ),
158+ stdoutFd = stdoutPipe(0 ),
159+ stderrFd = stderrPipe(0 )
160+ )
161+ }
135162 }
136163 }
137164
@@ -144,7 +171,7 @@ private[process] trait ProcessesCompanionPlatform {
144171 val status = stackalloc[CInt ]()
145172 val r = waitpid(proc.pid, status, WNOHANG )
146173 if (r < 0 && errno.errno != ECHILD )
147- F .raiseError( new RuntimeException (s " waitpid failed: errno= ${errno.errno}" ) )
174+ throw new RuntimeException (s " waitpid failed: errno= ${errno.errno}" )
148175 ()
149176 }
150177
@@ -189,39 +216,59 @@ private[process] trait ProcessesCompanionPlatform {
189216 .to
190217 }
191218 } else {
192- fallbackExitValue(nativeProcess.pid).to
219+ fallbackExitValue(nativeProcess.pid)
193220 }
194221 }
195222 } else {
196- fallbackExitValue(nativeProcess.pid).to
223+ fallbackExitValue(nativeProcess.pid)
197224 }
198225
199- def stdin : Pipe [F , Byte , Nothing ] = writeFd(nativeProcess.stdinFd)
200-
226+ def stdin : Pipe [F , Byte , Nothing ] = { in =>
227+ in
228+ .through(writeFd(nativeProcess.stdinFd))
229+ .onFinalize {
230+ F .blocking {
231+ close(nativeProcess.stdinFd)
232+ }.void
233+ }
234+ }
201235 def stdout : Stream [F , Byte ] = readFd(nativeProcess.stdoutFd, 8192 )
236+ .onFinalize {
237+ F .blocking {
238+ close(nativeProcess.stdoutFd)
239+ }.void
240+ }
202241
203242 def stderr : Stream [F , Byte ] = readFd(nativeProcess.stderrFd, 8192 )
243+ .onFinalize {
244+ F .blocking {
245+ close(nativeProcess.stderrFd)
246+ }.void
247+ }
204248 }
205249 }
206250 }
207251
208- private def fallbackExitValue (pid : pid_t): IO [Int ] = {
209- def loop : IO [Int ] =
210- IO .blocking {
252+ private def fallbackExitValue (pid : pid_t): F [Int ] = {
253+ def loop : F [Int ] =
254+ F .delay {
211255 Zone { _ =>
212256 val status = stackalloc[CInt ]()
213257 val result = waitpid(pid, status, WNOHANG )
214- if (result == pid) Some (WEXITSTATUS (! status))
215- else if (result == 0 ) None
258+
259+ if (result == pid) {
260+ println(" bye bye process" )
261+ Some (WEXITSTATUS (! status))
262+ } else if (result == 0 ) None
216263 else throw new IOException (s " waitpid failed with errno: ${errno.errno}" )
217264 }
218265 }.flatMap {
219- case Some (code) => IO .pure(code)
220- case None => IO .sleep(100 .millis) >> loop
266+ case Some (code) => F .pure(code)
267+ case None => F .sleep(100 .millis) >> loop
221268 }
222269
223270 loop.onCancel {
224- IO .blocking {
271+ F .blocking {
225272 kill(pid, SIGKILL )
226273 ()
227274 }
@@ -230,10 +277,3 @@ private[process] trait ProcessesCompanionPlatform {
230277
231278 }
232279}
233-
234- case class NativeProcess (
235- pid : pid_t,
236- stdinFd : Int ,
237- stdoutFd : Int ,
238- stderrFd : Int
239- )
0 commit comments