612 lines
20 KiB
OCaml
612 lines
20 KiB
OCaml
|
|
(*---------------------------------------------------------------------------
|
||
|
|
Copyright (c) 2015 The bos programmers. All rights reserved.
|
||
|
|
Distributed under the ISC license, see terms at the end of the file.
|
||
|
|
---------------------------------------------------------------------------*)
|
||
|
|
|
||
|
|
open Astring
|
||
|
|
open Rresult
|
||
|
|
|
||
|
|
let unix_buffer_size = 65536 (* UNIX_BUFFER_SIZE 4.0.0 *)
|
||
|
|
|
||
|
|
(* Unix pretty printers *)
|
||
|
|
|
||
|
|
let pp_unix_error ppf e = Fmt.string ppf (Unix.error_message e)
|
||
|
|
let pp_process_status ppf = function
|
||
|
|
| Unix.WEXITED c -> Fmt.pf ppf "exited with %d" c
|
||
|
|
| Unix.WSIGNALED s -> Fmt.pf ppf "killed by signal %a" Fmt.Dump.signal s
|
||
|
|
| Unix.WSTOPPED s -> Fmt.pf ppf "stopped by signal %a" Fmt.Dump.signal s
|
||
|
|
|
||
|
|
(* Error messages *)
|
||
|
|
|
||
|
|
let err_empty_line = "no command, empty command line"
|
||
|
|
let err_file f e = R.error_msgf "%a: %a" Fpath.pp f pp_unix_error e
|
||
|
|
let err_run cmd pp e = R.error_msgf "run %a: %a" Bos_cmd.dump cmd pp e
|
||
|
|
|
||
|
|
(* Primitives from Unix *)
|
||
|
|
|
||
|
|
let rec waitpid flags pid = try Unix.waitpid flags pid with
|
||
|
|
| Unix.Unix_error (Unix.EINTR, _, _) -> waitpid flags pid
|
||
|
|
|
||
|
|
let rec create_process prog args stdin stdout stderr =
|
||
|
|
try Unix.create_process prog args stdin stdout stderr with
|
||
|
|
| Unix.Unix_error (Unix.EINTR, _, _) ->
|
||
|
|
create_process prog args stdin stdout stderr
|
||
|
|
|
||
|
|
let rec create_process_env prog args env stdin stdout stderr =
|
||
|
|
try Unix.create_process_env prog args env stdin stdout stderr with
|
||
|
|
| Unix.Unix_error (Unix.EINTR, _, _) ->
|
||
|
|
create_process_env prog args env stdin stdout stderr
|
||
|
|
|
||
|
|
let rec pipe () = try Unix.pipe () with
|
||
|
|
| Unix.Unix_error (Unix.EINTR, _, _) -> pipe ()
|
||
|
|
|
||
|
|
let rec set_close_on_exec fd = try Unix.set_close_on_exec fd with
|
||
|
|
| Unix.Unix_error (Unix.EINTR, _, _) -> set_close_on_exec fd
|
||
|
|
|
||
|
|
let rec clear_close_on_exec fd = try Unix.clear_close_on_exec fd with
|
||
|
|
| Unix.Unix_error (Unix.EINTR, _, _) -> clear_close_on_exec fd
|
||
|
|
|
||
|
|
let rec openfile fn mode perm = try Unix.openfile fn mode perm with
|
||
|
|
| Unix.Unix_error (Unix.EINTR, _, _) -> openfile fn mode perm
|
||
|
|
|
||
|
|
let rec close fd = try Unix.close fd with
|
||
|
|
| Unix.Unix_error (Unix.EINTR, _, _) -> close fd
|
||
|
|
|
||
|
|
let close_no_err fd = try close fd with e -> ()
|
||
|
|
|
||
|
|
let rec select r w e t = try Unix.select r w e t with
|
||
|
|
| Unix.Unix_error (Unix.EINTR, _, _) -> select r w e t
|
||
|
|
|
||
|
|
(* Process creation primitive. *)
|
||
|
|
|
||
|
|
let create_process cmd env ~stdin ~stdout ~stderr =
|
||
|
|
let log_header pid = "EXEC:" ^ String.of_int pid in
|
||
|
|
let line = Bos_cmd.to_list cmd in
|
||
|
|
let prog = try List.hd line with Failure _ -> failwith err_empty_line in
|
||
|
|
let line = Array.of_list line in
|
||
|
|
match env with
|
||
|
|
| None ->
|
||
|
|
let pid = create_process prog line stdin stdout stderr in
|
||
|
|
Bos_log.debug
|
||
|
|
(fun m -> m ~header:(log_header pid) "@[<1>%a@]" Bos_cmd.dump cmd);
|
||
|
|
pid
|
||
|
|
| Some env ->
|
||
|
|
let env = Bos_os_env.to_array env in
|
||
|
|
let pid = create_process_env prog line env stdin stdout stderr in
|
||
|
|
Bos_log.debug
|
||
|
|
(fun m -> m ~header:(log_header pid) "@[<v>%a@,%a@]"
|
||
|
|
Fmt.Dump.(array String.dump) env Bos_cmd.dump cmd);
|
||
|
|
pid
|
||
|
|
|
||
|
|
(* Tool existence and search *)
|
||
|
|
|
||
|
|
let default_path_sep = if Sys.win32 then ";" else ":"
|
||
|
|
let dir_sep = Fpath.dir_sep.[0]
|
||
|
|
let exe_is_path t = String.exists (Char.equal dir_sep) t
|
||
|
|
|
||
|
|
let tool_file ~dir tool = match dir.[String.length dir - 1] with
|
||
|
|
| c when c = dir_sep -> dir ^ tool
|
||
|
|
| _ -> String.concat ~sep:Fpath.dir_sep [dir; tool]
|
||
|
|
|
||
|
|
let search_in_path tool =
|
||
|
|
let rec loop tool = function
|
||
|
|
| "" -> None
|
||
|
|
| p ->
|
||
|
|
let dir, p = match String.cut ~sep:default_path_sep p with
|
||
|
|
| None -> p, ""
|
||
|
|
| Some (dir, p) -> dir, p
|
||
|
|
in
|
||
|
|
if dir = "" then loop tool p else
|
||
|
|
let tool_file = tool_file ~dir tool in
|
||
|
|
match Bos_os_file._is_executable tool_file with
|
||
|
|
| false -> loop tool p
|
||
|
|
| true -> Some (Fpath.v tool_file)
|
||
|
|
in
|
||
|
|
try loop tool (Unix.getenv "PATH") with
|
||
|
|
| Not_found -> None
|
||
|
|
|
||
|
|
let search_in_dirs ~dirs tool =
|
||
|
|
let rec loop tool = function
|
||
|
|
| [] -> None
|
||
|
|
| d :: dirs ->
|
||
|
|
let tool_file = tool_file ~dir:(Fpath.to_string d) tool in
|
||
|
|
match Bos_os_file._is_executable tool_file with
|
||
|
|
| false -> loop tool dirs
|
||
|
|
| true -> Some (Fpath.v tool_file)
|
||
|
|
in
|
||
|
|
loop tool dirs
|
||
|
|
|
||
|
|
let ensure_exe_suffix_if_win32 = match Sys.win32 with
|
||
|
|
| false -> fun t -> t
|
||
|
|
| true ->
|
||
|
|
fun t -> match String.is_suffix ~affix:".exe" t with
|
||
|
|
| true -> t
|
||
|
|
| false -> t ^ ".exe"
|
||
|
|
|
||
|
|
let _find_tool ?search tool = match tool with
|
||
|
|
| "" -> Ok None
|
||
|
|
| tool ->
|
||
|
|
let tool = ensure_exe_suffix_if_win32 tool in
|
||
|
|
match exe_is_path tool with
|
||
|
|
| true ->
|
||
|
|
begin match Fpath.of_string tool with
|
||
|
|
| Ok t -> Ok (Some t)
|
||
|
|
| Error (`Msg _) as e -> e
|
||
|
|
end
|
||
|
|
| false ->
|
||
|
|
match search with
|
||
|
|
| None -> Ok (search_in_path tool)
|
||
|
|
| Some dirs -> Ok (search_in_dirs ~dirs tool)
|
||
|
|
|
||
|
|
let find_tool ?search cmd = match Bos_cmd.to_list cmd with
|
||
|
|
| [] -> Ok None
|
||
|
|
| c :: _ -> _find_tool ?search c
|
||
|
|
|
||
|
|
let err_not_found ?search cmd = match Bos_cmd.is_empty cmd with
|
||
|
|
| true -> R.error_msg err_empty_line
|
||
|
|
| false ->
|
||
|
|
let pp_search ppf = function
|
||
|
|
| None -> Fmt.string ppf "PATH"
|
||
|
|
| Some dirs ->
|
||
|
|
let pp_dir ppf d = Fmt.string ppf (Filename.quote @@ Fpath.to_string d)
|
||
|
|
in
|
||
|
|
Fmt.(list ~sep:(Fmt.any ",@ ") pp_dir) ppf dirs
|
||
|
|
in
|
||
|
|
let tool = List.hd @@ Bos_cmd.to_list cmd in
|
||
|
|
R.error_msgf "%s: no such command in %a" tool pp_search search
|
||
|
|
|
||
|
|
let get_tool ?search cmd = match find_tool ?search cmd with
|
||
|
|
| Ok (Some t) -> Ok t
|
||
|
|
| Ok None -> err_not_found ?search cmd
|
||
|
|
| Error _ as e -> e
|
||
|
|
|
||
|
|
let exists ?search cmd = match find_tool ?search cmd with
|
||
|
|
| Ok (Some _) -> Ok true
|
||
|
|
| Ok None -> Ok false
|
||
|
|
| Error _ as e -> e
|
||
|
|
|
||
|
|
let must_exist ?search cmd = match find_tool ?search cmd with
|
||
|
|
| Ok (Some _) -> Ok cmd
|
||
|
|
| Ok None -> err_not_found ?search cmd
|
||
|
|
| Error _ as e -> e
|
||
|
|
|
||
|
|
let resolve ?search cmd = match find_tool ?search cmd with
|
||
|
|
| Ok (Some t) ->
|
||
|
|
let t = Fpath.to_string t in
|
||
|
|
Ok (Bos_cmd.of_list (t :: List.tl (Bos_cmd.to_list cmd)))
|
||
|
|
| Ok None -> err_not_found ?search cmd
|
||
|
|
| Error _ as e -> e
|
||
|
|
|
||
|
|
let search_path_dirs ?(sep = default_path_sep) path =
|
||
|
|
let rec loop acc = function
|
||
|
|
| "" -> Ok (List.rev acc)
|
||
|
|
| p ->
|
||
|
|
let dir, p = match String.cut ~sep p with
|
||
|
|
| None -> p, ""
|
||
|
|
| Some (dir, p) -> dir, p
|
||
|
|
in
|
||
|
|
if dir = "" then loop acc p else
|
||
|
|
match Fpath.of_string dir with
|
||
|
|
| Error (`Msg m) -> R.error_msgf "search path value %S: %s" path m
|
||
|
|
| Ok d -> loop (d :: acc) p
|
||
|
|
in
|
||
|
|
loop [] path
|
||
|
|
|
||
|
|
(* Fd utils *)
|
||
|
|
|
||
|
|
module Fds = struct
|
||
|
|
|
||
|
|
(* Maintains a set of fds to close, standard fds are never in the set. *)
|
||
|
|
|
||
|
|
module Fd = struct
|
||
|
|
type t = Unix.file_descr
|
||
|
|
let compare : t -> t -> int = compare
|
||
|
|
end
|
||
|
|
module S = Set.Make (Fd)
|
||
|
|
|
||
|
|
type t = S.t ref
|
||
|
|
let empty () = ref S.empty
|
||
|
|
let rem fd s = s := S.remove fd !s
|
||
|
|
let add fd s =
|
||
|
|
if fd = Unix.stdin || fd = Unix.stdout || fd = Unix.stderr then () else
|
||
|
|
(s := S.add fd !s)
|
||
|
|
|
||
|
|
let close_all s = S.iter close_no_err !s; s := S.empty
|
||
|
|
let close fd s = if S.mem fd !s then (close_no_err fd; s := S.remove fd !s)
|
||
|
|
end
|
||
|
|
|
||
|
|
let write_fd_for_file ~append f =
|
||
|
|
try
|
||
|
|
let flags = Unix.([O_WRONLY; O_CREAT]) in
|
||
|
|
let flags = (if append then Unix.O_APPEND else Unix.O_TRUNC) :: flags in
|
||
|
|
Ok (openfile (Fpath.to_string f) flags 0o644)
|
||
|
|
with Unix.Unix_error (e, _, _) -> err_file f e
|
||
|
|
|
||
|
|
let read_fd_for_file f =
|
||
|
|
try Ok (openfile (Fpath.to_string f) [Unix.O_RDONLY] 0o644)
|
||
|
|
with Unix.Unix_error (e, _, _) -> err_file f e
|
||
|
|
|
||
|
|
let string_of_fd_async fd =
|
||
|
|
let len = unix_buffer_size in
|
||
|
|
let buf = Buffer.create len in
|
||
|
|
let b = Bytes.create len in
|
||
|
|
let rec step fd store b () =
|
||
|
|
try match Unix.read fd b 0 len with
|
||
|
|
| 0 -> `Ok (Buffer.contents buf)
|
||
|
|
| n ->
|
||
|
|
(* FIXME After 4.01 Buffer.add_subbytes buf b 0 n; step fd store b () *)
|
||
|
|
Buffer.add_substring buf (Bytes.unsafe_to_string b) 0 n;
|
||
|
|
step fd store b ()
|
||
|
|
with
|
||
|
|
| Unix.Unix_error (Unix.EPIPE, _, _) when Sys.win32 ->
|
||
|
|
(* That's the Windows way to say end, see
|
||
|
|
https://msdn.microsoft.com/en-us/library/windows/\
|
||
|
|
desktop/aa365467(v=vs.85).aspx *)
|
||
|
|
`Ok (Buffer.contents buf)
|
||
|
|
| Unix.Unix_error (Unix.EINTR, _, _) -> step fd buf b ()
|
||
|
|
| Unix.Unix_error ((Unix.EWOULDBLOCK | Unix.EAGAIN), _, _) ->
|
||
|
|
`Await (step fd buf b)
|
||
|
|
in
|
||
|
|
step fd buf b
|
||
|
|
|
||
|
|
let string_of_fd fd =
|
||
|
|
let rec loop = function `Ok s -> s | `Await step -> loop (step ()) in
|
||
|
|
loop (string_of_fd_async fd ())
|
||
|
|
|
||
|
|
let string_to_fd_async s fd =
|
||
|
|
let rec step fd s first len () =
|
||
|
|
(* FIXME After 4.01 try match Unix.single_write_substring fd s first len with *)
|
||
|
|
let b = Bytes.unsafe_of_string s in
|
||
|
|
try match Unix.single_write fd b first len with
|
||
|
|
| c when c = len -> `Ok ()
|
||
|
|
| c -> step fd s (first + c) (len - c) ()
|
||
|
|
with
|
||
|
|
| Unix.Unix_error (Unix.EINTR, _, _) -> step fd s first len ()
|
||
|
|
| Unix.Unix_error ((Unix.EWOULDBLOCK | Unix.EAGAIN), _, _) ->
|
||
|
|
`Await (step fd s first len)
|
||
|
|
in
|
||
|
|
step fd s 0 (String.length s)
|
||
|
|
|
||
|
|
let string_to_fd s fd =
|
||
|
|
let rec loop = function `Ok () -> () | `Await step -> loop (step ()) in
|
||
|
|
loop (string_to_fd_async s fd ())
|
||
|
|
|
||
|
|
let string_to_of_fd s ~to_fd ~of_fd =
|
||
|
|
let never () = assert false in
|
||
|
|
let wset, write = [to_fd], string_to_fd_async s to_fd in
|
||
|
|
let rset, read = [of_fd], string_of_fd_async of_fd in
|
||
|
|
let ret = ref "" in
|
||
|
|
let rec loop rset read wset write =
|
||
|
|
let rable, wable, _ = select rset wset [] (-1.) in
|
||
|
|
let rset, read = match rable with
|
||
|
|
| [] -> rset, read
|
||
|
|
| _ ->
|
||
|
|
match read () with
|
||
|
|
| `Ok s -> ret := s; [], never
|
||
|
|
| `Await step -> rset, step
|
||
|
|
in
|
||
|
|
let wset, write = match wable with
|
||
|
|
| [] -> wset, write
|
||
|
|
| _ ->
|
||
|
|
match write () with
|
||
|
|
| `Ok () -> close_no_err to_fd; [], never
|
||
|
|
| `Await step -> wset, step
|
||
|
|
in
|
||
|
|
if rset = [] && wset = [] then !ret else
|
||
|
|
loop rset read wset write
|
||
|
|
in
|
||
|
|
let sigpipe =
|
||
|
|
if Sys.win32 then None else
|
||
|
|
Some (Sys.signal Sys.sigpipe Sys.Signal_ignore)
|
||
|
|
in
|
||
|
|
let restore () = match sigpipe with
|
||
|
|
| None -> ()
|
||
|
|
| Some sigpipe -> Sys.set_signal Sys.sigpipe sigpipe
|
||
|
|
in
|
||
|
|
try let ret = loop rset read wset write in restore (); ret
|
||
|
|
with e -> restore (); raise e
|
||
|
|
|
||
|
|
(* Command runs *)
|
||
|
|
|
||
|
|
(* Run statuses *)
|
||
|
|
|
||
|
|
type status = [ `Exited of int | `Signaled of int ]
|
||
|
|
|
||
|
|
type run_info = Bos_cmd.t
|
||
|
|
let run_info_cmd ri = ri
|
||
|
|
|
||
|
|
let pp_status ppf = function
|
||
|
|
| `Exited c -> Fmt.pf ppf "exited with %d" c
|
||
|
|
| `Signaled s -> Fmt.pf ppf "killed by signal %a" Fmt.Dump.signal s
|
||
|
|
|
||
|
|
type run_status = run_info * status
|
||
|
|
|
||
|
|
let success = function
|
||
|
|
| Ok (v, (_, `Exited 0)) -> Ok v
|
||
|
|
| Ok (_, (cmd, s)) -> err_run cmd pp_status s
|
||
|
|
| Error _ as e -> e
|
||
|
|
|
||
|
|
(* Run standard errors *)
|
||
|
|
|
||
|
|
type run_err =
|
||
|
|
| Err_file of Fpath.t * bool
|
||
|
|
| Err_fd of Unix.file_descr
|
||
|
|
| Err_run_out
|
||
|
|
| Err_stderr
|
||
|
|
|
||
|
|
let err_file ?(append = false) f = Err_file (f, append)
|
||
|
|
let err_null = err_file Bos_os_file.null
|
||
|
|
let err_run_out = Err_run_out
|
||
|
|
let err_stderr = Err_stderr
|
||
|
|
|
||
|
|
let fd_for_run_err out_fd = function
|
||
|
|
| Err_file (f, append) -> write_fd_for_file ~append f
|
||
|
|
| Err_fd fd -> Ok fd
|
||
|
|
| Err_run_out -> Ok out_fd
|
||
|
|
| Err_stderr -> Ok Unix.stderr
|
||
|
|
|
||
|
|
(* Run standard inputs *)
|
||
|
|
|
||
|
|
type pipeline =
|
||
|
|
{ write : (string * Unix.file_descr) option;
|
||
|
|
read : Unix.file_descr;
|
||
|
|
pids : (Bos_cmd.t * int) list }
|
||
|
|
|
||
|
|
type run_in =
|
||
|
|
| In_string of string
|
||
|
|
| In_file of Fpath.t
|
||
|
|
| In_run_out of pipeline
|
||
|
|
| In_fd of Unix.file_descr
|
||
|
|
|
||
|
|
let in_string s = In_string s
|
||
|
|
let in_file f = In_file f
|
||
|
|
let in_null = in_file Bos_os_file.null
|
||
|
|
let in_stdin = In_fd Unix.stdin
|
||
|
|
|
||
|
|
(* Run standard outputs *)
|
||
|
|
|
||
|
|
type _ _run_out =
|
||
|
|
| To_string : (string * run_status) _run_out
|
||
|
|
| To_file : Fpath.t * bool -> (unit * run_status) _run_out
|
||
|
|
| To_run_in : run_in _run_out
|
||
|
|
| To_fd : Unix.file_descr -> (unit * run_status) _run_out
|
||
|
|
|
||
|
|
type run_out =
|
||
|
|
{ env : Bos_os_env.t option;
|
||
|
|
cmd : Bos_cmd.t;
|
||
|
|
run_err : run_err;
|
||
|
|
run_in : run_in; }
|
||
|
|
|
||
|
|
(* Waiting for processes *)
|
||
|
|
|
||
|
|
let rec wait_pids rev_pids = (* On failure returns the first failure *)
|
||
|
|
let rec loop ret = function
|
||
|
|
| (cmd, pid) :: pids ->
|
||
|
|
let s = snd (waitpid [] pid) in
|
||
|
|
if ret <> None then loop ret pids else
|
||
|
|
begin match s with
|
||
|
|
| Unix.WEXITED 0 -> loop ret pids
|
||
|
|
| Unix.WEXITED c -> loop (Some (cmd, `Exited c)) pids
|
||
|
|
| Unix.WSIGNALED s -> loop (Some (cmd, `Signaled s)) pids
|
||
|
|
| Unix.WSTOPPED _ -> assert false
|
||
|
|
end
|
||
|
|
| [] ->
|
||
|
|
match ret with
|
||
|
|
| None -> (fst (List.hd rev_pids), `Exited 0)
|
||
|
|
| Some s -> s
|
||
|
|
in
|
||
|
|
loop None (List.rev rev_pids)
|
||
|
|
|
||
|
|
(* Running *)
|
||
|
|
|
||
|
|
let do_in_fd_read_stdout stdin o pids do_read =
|
||
|
|
let fds = Fds.empty () in
|
||
|
|
try
|
||
|
|
Fds.add stdin fds;
|
||
|
|
let read_stdout, stdout = pipe () in
|
||
|
|
Fds.add read_stdout fds;
|
||
|
|
Fds.add stdout fds;
|
||
|
|
match fd_for_run_err stdout o.run_err with
|
||
|
|
| Error _ as e -> Fds.close_all fds; e
|
||
|
|
| Ok stderr ->
|
||
|
|
Fds.add stderr fds;
|
||
|
|
set_close_on_exec read_stdout; (* child close *)
|
||
|
|
let pid = create_process o.cmd o.env ~stdin ~stdout ~stderr in
|
||
|
|
clear_close_on_exec read_stdout; (* not in further childs (pipes) *)
|
||
|
|
Fds.close stdin fds;
|
||
|
|
Fds.close stdout fds;
|
||
|
|
do_read fds read_stdout ((o.cmd, pid) :: pids)
|
||
|
|
with
|
||
|
|
| Failure msg -> Error (`Msg msg)
|
||
|
|
| Unix.Unix_error (e, _, _) ->
|
||
|
|
Fds.close_all fds; err_run o.cmd pp_unix_error e
|
||
|
|
|
||
|
|
let do_in_fd_out_string stdin o pids =
|
||
|
|
do_in_fd_read_stdout stdin o pids
|
||
|
|
begin fun fds read_stdout pids ->
|
||
|
|
let res = string_of_fd read_stdout in
|
||
|
|
let ret = wait_pids pids in
|
||
|
|
Fds.close_all fds;
|
||
|
|
Ok (res, ret)
|
||
|
|
end
|
||
|
|
|
||
|
|
let do_in_fd_out_run_in stdin o pids =
|
||
|
|
do_in_fd_read_stdout stdin o pids
|
||
|
|
begin fun fds read_stdout pids ->
|
||
|
|
Fds.rem read_stdout fds;
|
||
|
|
Fds.close_all fds;
|
||
|
|
Ok (In_run_out { write = None; read = read_stdout; pids })
|
||
|
|
end
|
||
|
|
|
||
|
|
let do_in_fd_out_fd stdin stdout o pids =
|
||
|
|
let fds = Fds.empty () in
|
||
|
|
try
|
||
|
|
Fds.add stdin fds;
|
||
|
|
Fds.add stdout fds;
|
||
|
|
match fd_for_run_err stdout o.run_err with
|
||
|
|
| Error _ as e -> Fds.close_all fds; e
|
||
|
|
| Ok stderr ->
|
||
|
|
Fds.add stderr fds;
|
||
|
|
let pid = create_process o.cmd o.env ~stdin ~stdout ~stderr in
|
||
|
|
let ret = wait_pids ((o.cmd, pid) :: pids) in
|
||
|
|
Fds.close_all fds;
|
||
|
|
Ok ((), ret)
|
||
|
|
with
|
||
|
|
| Failure msg -> Error (`Msg msg)
|
||
|
|
| Unix.Unix_error (e, _, _) ->
|
||
|
|
Fds.close_all fds; err_run o.cmd pp_unix_error e
|
||
|
|
|
||
|
|
let do_in_run_out_string p o = do_in_fd_out_string p.read o p.pids
|
||
|
|
let do_in_run_out_run_in p o = do_in_fd_out_run_in p.read o p.pids
|
||
|
|
let do_in_run_out_fd p out_fd o = do_in_fd_out_fd p.read out_fd o p.pids
|
||
|
|
|
||
|
|
let do_in_string_read_stdout s o do_read =
|
||
|
|
let fds = Fds.empty () in
|
||
|
|
try
|
||
|
|
let stdin, write_stdin = pipe () in
|
||
|
|
Fds.add stdin fds;
|
||
|
|
Fds.add write_stdin fds;
|
||
|
|
let read_stdout, stdout = pipe () in
|
||
|
|
Fds.add read_stdout fds;
|
||
|
|
Fds.add stdout fds;
|
||
|
|
match fd_for_run_err stdout o.run_err with
|
||
|
|
| Error _ as e -> Fds.close_all fds; e
|
||
|
|
| Ok stderr ->
|
||
|
|
Fds.add stderr fds;
|
||
|
|
set_close_on_exec read_stdout; (* child close *)
|
||
|
|
set_close_on_exec write_stdin; (* child close *)
|
||
|
|
let pid = create_process o.cmd o.env ~stdin ~stdout ~stderr in
|
||
|
|
Fds.close stdin fds;
|
||
|
|
Fds.close stdout fds;
|
||
|
|
do_read fds write_stdin read_stdout pid
|
||
|
|
with
|
||
|
|
| Failure msg -> Error (`Msg msg)
|
||
|
|
| Unix.Unix_error (e, _, _) ->
|
||
|
|
Fds.close_all fds; err_run o.cmd pp_unix_error e
|
||
|
|
|
||
|
|
let do_in_string_out_string s o =
|
||
|
|
do_in_string_read_stdout s o
|
||
|
|
begin fun fds write_stdin read_stdout pid ->
|
||
|
|
let res = string_to_of_fd s ~to_fd:write_stdin ~of_fd:read_stdout in
|
||
|
|
Fds.close write_stdin fds; (* signal EOF *)
|
||
|
|
let ret = wait_pids [(o.cmd, pid)] in
|
||
|
|
Fds.close_all fds;
|
||
|
|
Ok (res, ret)
|
||
|
|
end
|
||
|
|
|
||
|
|
let do_in_string_out_run_in s o =
|
||
|
|
do_in_string_read_stdout s o
|
||
|
|
begin fun fds write_stdin read_stdout pid ->
|
||
|
|
Fds.rem read_stdout fds;
|
||
|
|
Fds.close_all fds;
|
||
|
|
Ok (In_run_out { write = Some (s, write_stdin);
|
||
|
|
read = read_stdout; pids = [o.cmd, pid] })
|
||
|
|
end
|
||
|
|
|
||
|
|
let do_in_string_out_fd s stdout o =
|
||
|
|
let fds = Fds.empty () in
|
||
|
|
try
|
||
|
|
Fds.add stdout fds;
|
||
|
|
let stdin, write_stdin = pipe () in
|
||
|
|
Fds.add stdin fds;
|
||
|
|
Fds.add write_stdin fds;
|
||
|
|
match fd_for_run_err stdout o.run_err with
|
||
|
|
| Error _ as e -> Fds.close_all fds; e
|
||
|
|
| Ok stderr ->
|
||
|
|
Fds.add stderr fds;
|
||
|
|
set_close_on_exec write_stdin; (* child close *)
|
||
|
|
let pid = create_process o.cmd o.env ~stdin ~stdout ~stderr in
|
||
|
|
string_to_fd s write_stdin;
|
||
|
|
Fds.close write_stdin fds; (* signal EOF *)
|
||
|
|
let ret = wait_pids [(o.cmd, pid)] in
|
||
|
|
Fds.close_all fds;
|
||
|
|
Ok ((), ret)
|
||
|
|
with
|
||
|
|
| Failure msg -> Error (`Msg msg)
|
||
|
|
| Unix.Unix_error (e, _, _) ->
|
||
|
|
Fds.close_all fds; err_run o.cmd pp_unix_error e
|
||
|
|
|
||
|
|
let do_in_fd :
|
||
|
|
type a. Unix.file_descr -> run_out -> a _run_out -> (a, [> R.msg]) result =
|
||
|
|
fun in_fd o ret -> match ret with
|
||
|
|
| To_string -> do_in_fd_out_string in_fd o []
|
||
|
|
| To_run_in -> do_in_fd_out_run_in in_fd o []
|
||
|
|
| To_fd out_fd -> do_in_fd_out_fd in_fd out_fd o []
|
||
|
|
| To_file (f, append) ->
|
||
|
|
write_fd_for_file ~append f >>= fun fd -> do_in_fd_out_fd in_fd fd o []
|
||
|
|
|
||
|
|
let run_cmd : type a. run_out -> a _run_out -> (a, [> R.msg]) result =
|
||
|
|
fun o ret -> match o.run_in with
|
||
|
|
| In_string s ->
|
||
|
|
begin match ret with
|
||
|
|
| To_string -> do_in_string_out_string s o
|
||
|
|
| To_run_in -> do_in_string_out_run_in s o
|
||
|
|
| To_fd out_fd -> do_in_string_out_fd s out_fd o
|
||
|
|
| To_file (f, append) ->
|
||
|
|
write_fd_for_file ~append f >>= fun fd -> do_in_string_out_fd s fd o
|
||
|
|
end
|
||
|
|
| In_run_out p ->
|
||
|
|
begin match ret with
|
||
|
|
| To_string -> do_in_run_out_string p o
|
||
|
|
| To_run_in -> do_in_run_out_run_in p o
|
||
|
|
| To_fd out_fd -> do_in_run_out_fd p out_fd o
|
||
|
|
| To_file (f, append) ->
|
||
|
|
write_fd_for_file ~append f >>= fun fd -> do_in_run_out_fd p fd o
|
||
|
|
end
|
||
|
|
| In_fd fd -> do_in_fd fd o ret
|
||
|
|
| In_file f -> read_fd_for_file f >>= fun fd -> do_in_fd fd o ret
|
||
|
|
|
||
|
|
let out_string ?(trim = true) o = match run_cmd o To_string with
|
||
|
|
| Ok (s, st) when trim -> Ok (String.trim s, st)
|
||
|
|
| r -> r
|
||
|
|
|
||
|
|
let out_lines ?trim o =
|
||
|
|
out_string ?trim o >>= fun (s, st) ->
|
||
|
|
Ok ((if s = "" then [] else String.cuts ~sep:"\n" s), st)
|
||
|
|
|
||
|
|
let out_file ?(append = false) f o = run_cmd o (To_file (f, append))
|
||
|
|
let out_run_in o = run_cmd o To_run_in
|
||
|
|
let out_null o = out_file Bos_os_file.null o
|
||
|
|
let out_stdout o = run_cmd o (To_fd Unix.stdout)
|
||
|
|
|
||
|
|
let to_string ?trim o = out_string ?trim o |> success
|
||
|
|
let to_lines ?trim o = out_lines ?trim o |> success
|
||
|
|
let to_file ?append f o = out_file ?append f o |> success
|
||
|
|
let to_null o = out_null o |> success
|
||
|
|
let to_stdout o = out_stdout o |> success
|
||
|
|
|
||
|
|
let run_io ?env ?err:(run_err = Err_stderr) cmd run_in =
|
||
|
|
{ env; cmd; run_err; run_in }
|
||
|
|
|
||
|
|
let run_out ?env ?err cmd = run_io ?env ?err cmd in_stdin
|
||
|
|
let run_in ?env ?err cmd i = run_io ?env ?err cmd i |> to_stdout
|
||
|
|
let run ?env ?err cmd = run_io ?env ?err cmd in_stdin |> to_stdout
|
||
|
|
let run_status ?env ?err ?(quiet = false) cmd =
|
||
|
|
let err = match err with
|
||
|
|
| None -> if quiet then err_null else err_stderr
|
||
|
|
| Some err -> err
|
||
|
|
in
|
||
|
|
let ret = match quiet with
|
||
|
|
| true -> in_null |> run_io ?env ~err cmd |> out_null
|
||
|
|
| false -> in_stdin |> run_io ?env ~err cmd |> out_stdout
|
||
|
|
in
|
||
|
|
match ret with
|
||
|
|
| Ok ((), (_, status)) -> Ok status
|
||
|
|
| Error _ as e -> e
|
||
|
|
|
||
|
|
(*---------------------------------------------------------------------------
|
||
|
|
Copyright (c) 2015 The bos programmers
|
||
|
|
|
||
|
|
Permission to use, copy, modify, and/or 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.
|
||
|
|
---------------------------------------------------------------------------*)
|