File: process.ml

package info (click to toggle)
ocaml-process 0.2.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 152 kB
  • sloc: ml: 375; makefile: 38
file content (301 lines) | stat: -rw-r--r-- 9,026 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
(*
 * Copyright (c) 2015 David Sheets <sheets@alum.mit.edu>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 *)

module Signal = struct
  type t =
    | SIGABRT
    | SIGALRM
    | SIGFPE
    | SIGHUP
    | SIGILL
    | SIGINT
    | SIGKILL
    | SIGPIPE
    | SIGQUIT
    | SIGSEGV
    | SIGTERM
    | SIGUSR1
    | SIGUSR2
    | SIGCHLD
    | SIGCONT
    | SIGSTOP
    | SIGTSTP
    | SIGTTIN
    | SIGTTOU
    | SIGVTALRM
    | SIGPROF
    | Unknown of int

  let of_int = Sys.(function
    | x when x = sigabrt   -> SIGABRT
    | x when x = sigalrm   -> SIGALRM
    | x when x = sigfpe    -> SIGFPE
    | x when x = sighup    -> SIGHUP
    | x when x = sigill    -> SIGILL
    | x when x = sigint    -> SIGINT
    | x when x = sigkill   -> SIGKILL
    | x when x = sigpipe   -> SIGPIPE
    | x when x = sigquit   -> SIGQUIT
    | x when x = sigsegv   -> SIGSEGV
    | x when x = sigterm   -> SIGTERM
    | x when x = sigusr1   -> SIGUSR1
    | x when x = sigusr2   -> SIGUSR2
    | x when x = sigchld   -> SIGCHLD
    | x when x = sigcont   -> SIGCONT
    | x when x = sigstop   -> SIGSTOP
    | x when x = sigtstp   -> SIGTSTP
    | x when x = sigttin   -> SIGTTIN
    | x when x = sigttou   -> SIGTTOU
    | x when x = sigvtalrm -> SIGVTALRM
    | x when x = sigprof   -> SIGPROF
    | x                    -> Unknown x
  )

  let to_string = function
    | SIGABRT -> "SIGABRT"
    | SIGALRM -> "SIGALRM"
    | SIGFPE -> "SIGFPE"
    | SIGHUP -> "SIGHUP"
    | SIGILL -> "SIGILL"
    | SIGINT -> "SIGINT"
    | SIGKILL -> "SIGKILL"
    | SIGPIPE -> "SIGPIPE"
    | SIGQUIT -> "SIGQUIT"
    | SIGSEGV -> "SIGSEGV"
    | SIGTERM -> "SIGTERM"
    | SIGUSR1 -> "SIGUSR1"
    | SIGUSR2 -> "SIGUSR2"
    | SIGCHLD -> "SIGCHLD"
    | SIGCONT -> "SIGCONT"
    | SIGSTOP -> "SIGSTOP"
    | SIGTSTP -> "SIGTSTP"
    | SIGTTIN -> "SIGTTIN"
    | SIGTTOU -> "SIGTTOU"
    | SIGVTALRM -> "SIGVTALRM"
    | SIGPROF -> "SIGPROF"
    | Unknown k -> "SIG"^(string_of_int k)

end

module Exit = struct
  type t =
    | Exit of int
    | Kill of Signal.t
    | Stop of Signal.t

  type error = {
    cwd     : string;
    command : string;
    args    : string array;
    status  : t;
  }
  exception Error of error

  let of_unix = Unix.(function
    | WEXITED k   -> Exit k
    | WSIGNALED k -> Kill (Signal.of_int k)
    | WSTOPPED k  -> Stop (Signal.of_int k)
  )

  let to_string = function
    | Exit k -> Printf.sprintf "exit %d" k
    | Kill k -> Printf.sprintf "kill %s" (Signal.to_string k)
    | Stop k -> Printf.sprintf "stop %s" (Signal.to_string k)

  let error_to_string { cwd; command; args; status } =
    let args = Array.map (Printf.sprintf "%S") args in
    let args_s = String.concat "; " (Array.to_list args) in
    Printf.sprintf "%s [|%s|] in %s: %s" command args_s cwd (to_string status)

  let check ?(exit_status=[0]) command args = function
    | Exit k when List.mem k exit_status -> ()
    | status ->
      raise (Error { cwd = Unix.getcwd (); command; args; status })

end

module Output = struct
  type t = {
    exit_status : Exit.t;
    stdout : string list;
    stderr : string list;
  }
end

module type S = sig
  type 'a io

  val run :
    ?stdin:Bytes.t -> ?exit_status:int list -> string -> string array
    -> Output.t io

  val read_stdout :
    ?stdin:Bytes.t -> ?exit_status:int list -> string -> string array
    -> string list io
end

module Blocking : S with type 'a io = 'a = struct
  type 'a io = 'a

  let quote = Printf.sprintf "\"%s\""

  let string_of_prog_args prog args =
    prog ^ (
      if Array.length args > 0 then
        " " ^ (String.concat " " Array.(to_list (map quote args)))
      else ""
    )

  let rec waitpid_retry flags pid =
    try Unix.waitpid flags pid
    with Unix.Unix_error (Unix.EINTR,"waitpid","") ->
      waitpid_retry flags pid

  let io_from_fd fds fn fd =
    let closed = fn fd in
    if closed
    then List.filter ((<>) fd) fds
    else fds

  let select_io
      ~input_stdout ~stdout
      ~input_stderr ~stderr
      ~output_stdin ~stdin
      ~read_fds ~write_fds =
    let rec loop ~read_fds ~write_fds =
      if read_fds <> [] || write_fds <> []
      then
        let ready_read, ready_write, _ready_exn =
          Unix.select read_fds write_fds [] ~-.1.
        in
        match ready_read with
        | fd::_ when fd = stdout ->
          let read_fds = io_from_fd read_fds input_stdout fd in
          loop ~read_fds ~write_fds
        | fd::_ when fd = stderr ->
          let read_fds = io_from_fd read_fds input_stderr fd in
          loop ~read_fds ~write_fds
        | _::_ -> failwith "unexpected read fd" (* TODO: ? *)
        | [] -> match ready_write with
          | fd::_ ->
            let write_fds = io_from_fd write_fds output_stdin fd in
            loop ~read_fds ~write_fds
          | [] -> failwith "select failed" (* TODO: ? *)
    in
    try
      let sigpipe = Sys.(signal sigpipe Signal_ignore) in
      loop ~read_fds ~write_fds;
      Sys.(set_signal sigpipe) sigpipe
    with Invalid_argument _ ->
      (* Can't ignore the pipe broken signal on Windows. *)
      loop ~read_fds ~write_fds

  let execute prog args ~output_stdin ~input_stdout ~input_stderr =
    let in_fd, stdin = Unix.pipe () in
    let stdout, out_fd = Unix.pipe () in
    let stderr, err_fd = Unix.pipe () in
    Unix.set_close_on_exec stdin;
    Unix.set_close_on_exec stdout;
    Unix.set_close_on_exec stderr;
    let args = Array.append [|prog|] args in
    let pid = Unix.create_process prog args in_fd out_fd err_fd in
    Unix.close in_fd;
    Unix.close out_fd;
    Unix.close err_fd;
    select_io
      ~input_stdout ~stdout
      ~input_stderr ~stderr
      ~output_stdin ~stdin
      ~read_fds:[ stdout; stderr ] ~write_fds:[ stdin ];
    (* stdin is closed when we run out of input *)
    Unix.close stdout;
    Unix.close stderr;
    let status = snd (waitpid_retry [Unix.WUNTRACED] pid) in
    Exit.of_unix status

  let rindex_from buf i c =
    try Some (Bytes.rindex_from buf i c) with Not_found -> None

  let rec lines buf i acc =
    match rindex_from buf i '\n' with
    | Some 0 -> Bytes.empty :: (Bytes.sub buf 1 i) :: acc
    | Some j -> lines buf (j - 1) (Bytes.sub buf (j + 1) (i - j) :: acc)
    | None -> Bytes.sub buf 0 (i + 1) :: acc

  let read_lines buf len into fd =
    (* The EPIPE case covers an odd behavior on Windows.
       See <http://caml.inria.fr/mantis/view.php?id=7342>.
    *)
    let n = Unix.(try read fd buf 0 len with Unix_error (EPIPE, _, _) -> 0) in
    if n = 0
    then true (* closed *)
    else
      let ls = lines buf (n - 1) [] in
      begin match !into with
        | [] -> into := List.rev ls
        | partial_line::rest -> match ls with
          | [] -> ()
          | first::more ->
            let first = Bytes.cat partial_line first in
            into := List.rev_append more (first :: rest)
      end;
      false (* not closed *)

  let run ?(stdin=Bytes.empty) ?exit_status prog args =
    let out_lines = ref [] in
    let err_lines = ref [] in
    let len = 4096 in
    let buf = Bytes.create len in
    let input_stdout = read_lines buf len out_lines in
    let input_stderr = read_lines buf len err_lines in
    let stdin_len = Bytes.length stdin in
    let stdin_off = ref 0 in
    let output_stdin i_fd =
      let off = !stdin_off in
      let len = stdin_len - off in
      if len = 0
      then begin
        Unix.close i_fd;
        true (* closed, we have nothing more to write *)
      end
      else
        try
          let n = Unix.single_write i_fd stdin off len in
          stdin_off := off + n;
          false (* not closed *)
        with
        | Unix.Unix_error (Unix.EPIPE, "single_write", _) -> true (* closed *)
    in
    let exit_status' =
      execute prog args ~output_stdin ~input_stdout ~input_stderr
    in
    (match exit_status with
     | None -> ()
     | Some exit_status -> Exit.check ~exit_status prog args exit_status'
    );
    let exit_status = exit_status' in
    let stdout = List.rev_map Bytes.to_string !out_lines in
    let stderr = List.rev_map Bytes.to_string !err_lines in
    Output.({ exit_status; stdout; stderr; })

  let read_stdout ?stdin ?exit_status prog args =
    let exit_status = match exit_status with None -> [0] | Some v -> v in
    (run ?stdin ~exit_status prog args).Output.stdout

end

include Blocking