Skip to content

Commit b68af32

Browse files
committed
Merge branch 'development' into copilot/add-hlcode-test-suite
2 parents ca16e68 + f2546fc commit b68af32

File tree

9 files changed

+180
-669
lines changed

9 files changed

+180
-669
lines changed

libs/extc/dune

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,7 @@
1212

1313
(library
1414
(name extproc)
15-
(foreign_stubs
16-
(language c)
17-
(names process_stubs))
15+
(libraries unix)
1816
(modules process)
1917
(wrapped false)
2018
)

libs/extc/process.ml

Lines changed: 135 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,136 @@
1-
(*
2-
* Extc : C common OCaml bindings
3-
* Copyright (c)2004-2015 Nicolas Cannasse
4-
*
5-
* This program is free software; you can redistribute it and/or modify
6-
* it under the terms of the GNU General Public License as published by
7-
* the Free Software Foundation; either version 2 of the License, or
8-
* (at your option) any later version.
9-
*
10-
* This program is distributed in the hope that it will be useful,
11-
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12-
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13-
* GNU General Public License for more details.
14-
*
15-
* You should have received a copy of the GNU General Public License
16-
* along with this program; if not, write to the Free Software
17-
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
18-
*)
19-
20-
type process
21-
22-
external run : string -> string array option -> process = "process_run"
23-
external read_stdout : process -> string -> int -> int -> int = "process_stdout_read"
24-
external read_stderr : process -> string -> int -> int -> int = "process_stderr_read"
25-
external write_stdin : process -> string -> int -> int -> int = "process_stdin_write"
26-
external close_stdin : process -> unit = "process_stdin_close"
27-
external exit : process -> int = "process_exit"
28-
external pid : process -> int = "process_pid"
29-
external close : process -> unit = "process_close"
30-
external kill : process -> unit = "process_kill"
1+
(** Pure OCaml implementation of subprocess management.
312
3+
Replaces the former C stubs (process_stubs.c) which were unsafe under
4+
OCaml 5 multi-domain execution (missing [caml_enter_blocking_section],
5+
direct [fork()] in multi-threaded processes).
6+
7+
Uses [Unix.create_process] which is domain-safe and handles
8+
[posix_spawn] on modern systems.
9+
10+
When the command is not found, [run] returns a process whose pipes
11+
immediately return EOF and whose [exit] returns code 127 (matching
12+
the POSIX shell convention and the old fork+exec behavior where
13+
fork always succeeded). *)
14+
15+
type process = {
16+
pid : int;
17+
stdin_fd : Unix.file_descr;
18+
stdout_fd : Unix.file_descr;
19+
stderr_fd : Unix.file_descr;
20+
mutable exit_code : int option;
21+
}
22+
23+
(** Returns a readable file_descr that immediately yields EOF. *)
24+
let make_eof_fd () =
25+
let (r, w) = Unix.pipe ~cloexec:true () in
26+
Unix.close w;
27+
r
28+
29+
(** Returns a writable file_descr where writes fail with EPIPE. *)
30+
let make_null_fd () =
31+
let (r, w) = Unix.pipe ~cloexec:true () in
32+
Unix.close r;
33+
w
34+
35+
let unix_error_msg err fn arg =
36+
Printf.sprintf "%s(%s): %s" fn arg (Unix.error_message err)
37+
38+
let run cmd args =
39+
let (child_stdin_r, child_stdin_w) = Unix.pipe ~cloexec:true () in
40+
let (child_stdout_r, child_stdout_w) = Unix.pipe ~cloexec:true () in
41+
let (child_stderr_r, child_stderr_w) = Unix.pipe ~cloexec:true () in
42+
let shell, argv = match args with
43+
| None ->
44+
if Sys.win32 then
45+
let comspec = try Sys.getenv "COMSPEC" with Not_found -> "cmd.exe" in
46+
comspec, [|comspec; "/C"; cmd|]
47+
else
48+
"/bin/sh", [|"/bin/sh"; "-c"; cmd|]
49+
| Some a ->
50+
cmd, Array.append [|cmd|] a
51+
in
52+
match
53+
try Ok (Unix.create_process shell argv child_stdin_r child_stdout_w child_stderr_w)
54+
with Unix.Unix_error _ as e -> Error e
55+
with
56+
| Ok pid ->
57+
Unix.close child_stdin_r;
58+
Unix.close child_stdout_w;
59+
Unix.close child_stderr_w;
60+
{ pid; stdin_fd = child_stdin_w; stdout_fd = child_stdout_r; stderr_fd = child_stderr_r; exit_code = None }
61+
| Error err ->
62+
Unix.close child_stdin_r;
63+
Unix.close child_stdin_w;
64+
Unix.close child_stdout_r;
65+
Unix.close child_stdout_w;
66+
Unix.close child_stderr_r;
67+
let stderr_r = match err with
68+
| (Unix.Unix_error (err, fn, arg)) ->
69+
(* Process creation failed (e.g. command not found).
70+
Match the old fork+exec behavior: return a process whose pipes
71+
immediately return EOF and whose exit code is 127.
72+
Write the error message to the stderr pipe so callers can read it. *)
73+
let errmsg = unix_error_msg err fn arg ^ "\n" in
74+
let (stderr_r, stderr_w) = Unix.pipe ~cloexec:true () in
75+
begin try
76+
ignore (Unix.write_substring stderr_w errmsg 0 (String.length errmsg))
77+
with Unix.Unix_error _ ->
78+
()
79+
end;
80+
Unix.close stderr_w;
81+
stderr_r
82+
| _ ->
83+
Unix.close child_stderr_w;
84+
make_eof_fd ()
85+
in
86+
{ pid = 0; stdin_fd = make_null_fd (); stdout_fd = make_eof_fd (); stderr_fd = stderr_r; exit_code = Some 127 }
87+
88+
let read_stdout p buf pos len =
89+
let n = try
90+
Unix.read p.stdout_fd (Bytes.unsafe_of_string buf) pos len
91+
with Unix.Unix_error (err, fn, arg) ->
92+
failwith (unix_error_msg err fn arg)
93+
in
94+
if n = 0 then failwith "process_stdout_read";
95+
n
96+
97+
let read_stderr p buf pos len =
98+
let n = try
99+
Unix.read p.stderr_fd (Bytes.unsafe_of_string buf) pos len
100+
with Unix.Unix_error (err, fn, arg) ->
101+
failwith (unix_error_msg err fn arg)
102+
in
103+
if n = 0 then failwith "process_stderr_read";
104+
n
105+
106+
let write_stdin p buf pos len =
107+
try Unix.write_substring p.stdin_fd buf pos len
108+
with Unix.Unix_error (err, fn, arg) -> failwith (unix_error_msg err fn arg)
109+
110+
let close_stdin p =
111+
try Unix.close p.stdin_fd
112+
with Unix.Unix_error (err, fn, arg) -> failwith (unix_error_msg err fn arg)
113+
114+
let exit p =
115+
match p.exit_code with
116+
| Some c -> c
117+
| None ->
118+
let _, status = Unix.waitpid [] p.pid in
119+
let c = match status with
120+
| Unix.WEXITED c -> c
121+
| Unix.WSIGNALED c -> c
122+
| Unix.WSTOPPED c -> c
123+
in
124+
p.exit_code <- Some c;
125+
c
126+
127+
let pid p = p.pid
128+
129+
let close p =
130+
(try Unix.close p.stdout_fd with Unix.Unix_error _ -> ());
131+
(try Unix.close p.stderr_fd with Unix.Unix_error _ -> ());
132+
(try Unix.close p.stdin_fd with Unix.Unix_error _ -> ())
133+
134+
let kill p =
135+
if p.exit_code = None && p.pid > 0 then
136+
(try Unix.kill p.pid Sys.sigkill with Unix.Unix_error _ -> ())

0 commit comments

Comments
 (0)