Skip to content

Commit 3e1f367

Browse files
Merge pull request #957 from MisterDA/cloexec-proc-dev-null-fixes
Fix null file descriptor being closed when used as redirection for standard fd of child processes
2 parents 1ad6d8f + 15c65b5 commit 3e1f367

File tree

5 files changed

+112
-39
lines changed

5 files changed

+112
-39
lines changed

CHANGES

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
===== dev =====
22

3+
====== Fixes ======
4+
5+
* Fix null file descriptor being closed when used as redirection for standard fd of child processes. (#957, Antonin Décimo)
6+
37
===== 5.6.0 =====
48

59
====== Installability ======

src/unix/lwt_process.cppo.ml

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ let win32_get_fd fd redirection =
3636
| `Keep ->
3737
Some fd
3838
| `Dev_null ->
39-
Some (Unix.openfile "nul" [Unix.O_RDWR; Unix.O_CLOEXEC] 0o666)
39+
Some (Unix.openfile "nul" [Unix.O_RDWR; Unix.O_KEEPEXEC] 0o666)
4040
| `Close ->
4141
None
4242
| `FD_copy fd' ->
@@ -122,18 +122,15 @@ let unix_redirect fd redirection = match redirection with
122122
| `Keep ->
123123
()
124124
| `Dev_null ->
125-
Unix.close fd;
126-
let dev_null = Unix.openfile "/dev/null" [Unix.O_RDWR; Unix.O_CLOEXEC] 0o666 in
127-
if fd <> dev_null then begin
128-
Unix.dup2 dev_null fd;
129-
Unix.close dev_null
130-
end
125+
let dev_null = Unix.openfile "/dev/null" [Unix.O_RDWR; Unix.O_KEEPEXEC] 0o666 in
126+
Unix.dup2 ~cloexec:false dev_null fd;
127+
Unix.close dev_null
131128
| `Close ->
132129
Unix.close fd
133130
| `FD_copy fd' ->
134-
Unix.dup2 fd' fd
131+
Unix.dup2 ~cloexec:false fd' fd
135132
| `FD_move fd' ->
136-
Unix.dup2 fd' fd;
133+
Unix.dup2 ~cloexec:false fd' fd;
137134
Unix.close fd'
138135

139136
#if OCAML_VERSION >= (5, 0, 0)

test/unix/dummy.ml

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,31 @@
1+
(* This file is part of Lwt, released under the MIT license. See LICENSE.md for
2+
details, or visit https://github.com/ocsigen/lwt/blob/master/LICENSE.md. *)
3+
4+
5+
6+
let test_input_str = "the quick brown fox jumps over the lazy dog"
7+
let test_input = Bytes.of_string test_input_str
8+
let test_input_len = Bytes.length test_input
9+
10+
let read () =
11+
let buf = Bytes.create test_input_len in
12+
let rec aux n =
13+
let i = Unix.read Unix.stdin buf n (Bytes.length buf - n) in
14+
if i = 0 || n + i = test_input_len then
15+
Bytes.equal buf test_input
16+
else aux (n + i)
17+
in
18+
if aux 0 then
19+
(* make sure there's nothing more to read *)
20+
0 = Unix.read Unix.stdin buf 0 1
21+
else false
22+
23+
let write fd =
24+
assert (test_input_len = Unix.write fd test_input 0 test_input_len)
25+
126
let () =
2-
let str = "the quick brown fox jumps over the lazy dog" in
327
match Sys.argv.(1) with
4-
| "read" -> if read_line () <> str then exit 1
5-
| "write" -> print_string str
28+
| "read" -> exit @@ if read () then 0 else 1
29+
| "write" -> write Unix.stdout
30+
| "errwrite" -> write Unix.stderr
631
| _ -> invalid_arg "Sys.argv"

test/unix/dune

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55

66
(executable
77
(name dummy)
8-
(modules dummy))
8+
(modules dummy)
9+
(libraries unix))
910

1011
(executable
1112
(name main)

test/unix/test_lwt_process.ml

Lines changed: 72 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,52 @@
66
open Test
77
open Lwt.Infix
88

9-
let expected = "the quick brown fox jumps over the lazy dog"
9+
let expected_str = "the quick brown fox jumps over the lazy dog"
10+
let expected = Bytes.of_string expected_str
11+
let expected_len = Bytes.length expected
12+
13+
let check_status ?(status=(=) 0) = function
14+
| Unix.WEXITED n when status n -> Lwt.return_true
15+
| Unix.WEXITED n ->
16+
Printf.eprintf "exited with code %d" n;
17+
Lwt.return_false
18+
| Unix.WSIGNALED x ->
19+
Printf.eprintf "failed with signal %d" x;
20+
Lwt.return_false
21+
| Unix.WSTOPPED x ->
22+
Printf.eprintf "stopped with signal %d" x;
23+
Lwt.return_false
24+
25+
let pwrite ~stdin pout =
26+
let args = [|"dummy.exe"; "read"|] in
27+
let proc = Lwt_process.exec ~stdin ("./dummy.exe", args) in
28+
let write = Lwt.finalize
29+
(fun () -> Lwt_unix.write pout expected 0 expected_len)
30+
(fun () -> Lwt_unix.close pout) in
31+
proc >>= fun r ->
32+
write >>= fun n ->
33+
assert (n = expected_len);
34+
check_status r
35+
36+
let pread ?stdout ?stderr pin =
37+
let buf = Bytes.create expected_len in
38+
let proc = match stdout, stderr with
39+
| Some stdout, None ->
40+
let args = [|"dummy.exe"; "write"|] in
41+
Lwt_process.exec ~stdout ("./dummy.exe", args)
42+
| None, Some stderr ->
43+
let args = [|"dummy.exe"; "errwrite"|] in
44+
Lwt_process.exec ~stderr ("./dummy.exe", args)
45+
| _ -> assert false
46+
in
47+
let read = Lwt_unix.read pin buf 0 expected_len in
48+
proc >>= fun r ->
49+
read >>= fun n ->
50+
assert (n = expected_len);
51+
assert (Bytes.equal buf expected);
52+
Lwt_unix.read pin buf 0 1 >>= fun n ->
53+
assert (n = 0);
54+
check_status r
1055

1156
let suite = suite "lwt_process" [
1257
(* The sleep command is not available on Win32. *)
@@ -20,42 +65,43 @@ let suite = suite "lwt_process" [
2065
(fun _ -> Lwt.return ""))
2166
>>= fun _ -> Lwt.return_true);
2267

23-
test "pread"
68+
test "subproc stdout can be redirected to null"
2469
(fun () ->
2570
let args = [|"dummy.exe"; "write"|] in
26-
Lwt_process.pread ~stdin:`Close ~stderr:`Close ("./dummy.exe", args)
27-
>|= fun actual ->
28-
actual = expected);
71+
Lwt_process.exec ~stdout:`Dev_null ("./dummy.exe", args)
72+
>>= check_status);
2973

30-
test "pread keep"
74+
test "subproc stderr can be redirected to null"
3175
(fun () ->
32-
let args = [|"dummy.exe"; "write"|] in
33-
Lwt_process.pread ~stdin:`Keep ~stderr:`Keep ("./dummy.exe", args)
34-
>|= fun actual ->
35-
actual = expected);
76+
let args = [|"dummy.exe"; "errwrite"|] in
77+
Lwt_process.exec ~stderr:`Dev_null ("./dummy.exe", args)
78+
>>= check_status);
3679

37-
test "pread nul"
80+
test "subproc cannot write on closed stdout"
3881
(fun () ->
3982
let args = [|"dummy.exe"; "write"|] in
40-
Lwt_process.pread ~stdin:`Dev_null ~stderr:`Dev_null ("./dummy.exe", args)
41-
>|= fun actual ->
42-
actual = expected);
83+
let stderr = `Dev_null (* mask subproc stderr *) in
84+
Lwt_process.exec ~stdout:`Close ~stderr ("./dummy.exe", args)
85+
>>= check_status ~status:((<>) 0));
86+
87+
test "subproc cannot write on closed stderr"
88+
(fun () ->
89+
let args = [|"dummy.exe"; "errwrite"|] in
90+
Lwt_process.exec ~stderr:`Close ("./dummy.exe", args)
91+
>>= check_status ~status:((<>) 0));
4392

44-
test "pwrite"
93+
test "can write to subproc stdin"
4594
(fun () ->
46-
let args = [|"dummy.exe"; "read"|] in
47-
Lwt_process.pwrite ~stdout:`Close ~stderr:`Close ("./dummy.exe", args) expected
48-
>>= fun () -> Lwt.return_true);
95+
let pin, pout = Lwt_unix.pipe_out ~cloexec:true () in
96+
pwrite ~stdin:(`FD_move pin) pout);
4997

50-
test "pwrite keep"
98+
test "can read from subproc stdout"
5199
(fun () ->
52-
let args = [|"dummy.exe"; "read"|] in
53-
Lwt_process.pwrite ~stdout:`Keep ~stderr:`Keep ("./dummy.exe", args) expected
54-
>>= fun () -> Lwt.return_true);
100+
let pin, pout = Lwt_unix.pipe_in ~cloexec:true () in
101+
pread ~stdout:(`FD_move pout) pin);
55102

56-
test "pwrite nul"
103+
test "can read from subproc stderr"
57104
(fun () ->
58-
let args = [|"dummy.exe"; "read"|] in
59-
Lwt_process.pwrite ~stdout:`Dev_null ~stderr:`Dev_null ("./dummy.exe", args) expected
60-
>>= fun () -> Lwt.return_true);
105+
let pin, perr = Lwt_unix.pipe_in ~cloexec:true () in
106+
pread ~stderr:(`FD_move perr) pin);
61107
]

0 commit comments

Comments
 (0)