external.ml (5649B)
1 (* Unison file synchronizer: src/external.ml *) 2 (* Copyright 1999-2020, Benjamin C. Pierce 3 4 This program is free software: you can redistribute it and/or modify 5 it under the terms of the GNU General Public License as published by 6 the Free Software Foundation, either version 3 of the License, or 7 (at your option) any later version. 8 9 This program is distributed in the hope that it will be useful, 10 but WITHOUT ANY WARRANTY; without even the implied warranty of 11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 GNU General Public License for more details. 13 14 You should have received a copy of the GNU General Public License 15 along with this program. If not, see <http://www.gnu.org/licenses/>. 16 *) 17 18 19 (*****************************************************************************) 20 (* RUNNING EXTERNAL PROGRAMS *) 21 (*****************************************************************************) 22 23 let debug = Util.debug "external" 24 25 let (>>=) = Lwt.bind 26 open Lwt 27 28 (* For backwards compatibility with OCaml < 4.12 *) 29 let path = 30 try 31 Str.split (Str.regexp (if Sys.win32 then ";" else ":")) 32 (Sys.getenv "PATH") 33 with Not_found -> 34 [] 35 36 let search_in_path ?(path = path) name = 37 if String.contains name '/' then name else 38 Filename.concat 39 (List.find (fun dir -> 40 let p = Filename.concat dir name in 41 let found = System.file_exists p in 42 debug (fun () -> Util.msg "'%s' ...%s\n" p 43 (match found with true -> "found" | false -> "not found")); 44 found) 45 path) 46 name 47 48 (* Make sure external process resources are collected and zombie processes 49 reaped when the Lwt thread calling the external program is stopped 50 suddenly due to remote connection being closed. *) 51 let close_process_noerr close pid x = 52 let pid = pid x in 53 begin try 54 Unix.kill pid (if Sys.win32 then Sys.sigkill else Sys.sigterm) 55 with Unix.Unix_error _ -> () end; 56 begin try ignore (Terminal.safe_waitpid pid) with Unix.Unix_error _ -> () end; 57 try ignore (close x) with Sys_error _ | Unix.Unix_error _ -> () 58 59 let inProcRes = 60 Remote.resourceWithConnCleanup System.close_process_in 61 (close_process_noerr System.close_process_in System.process_in_pid) 62 let fullProcRes = 63 Remote.resourceWithConnCleanup System.close_process_full 64 (close_process_noerr System.close_process_full System.process_full_pid) 65 66 let openProcessIn cmd = inProcRes.register (System.open_process_in cmd) 67 let closeProcessIn = inProcRes.release 68 69 (* Remove call to search_in_path once we require OCaml >= 4.12. *) 70 let openProcessArgsIn cmd args = inProcRes.register (System.open_process_args_in (search_in_path cmd) args) 71 let closeProcessArgsIn = inProcRes.release 72 73 let openProcessFull cmd = fullProcRes.register (System.open_process_full cmd) 74 let closeProcessFull = fullProcRes.release 75 76 (* Remove call to search_in_path once we require OCaml >= 4.12. *) 77 let openProcessArgsFull cmd args = fullProcRes.register (System.open_process_args_full (search_in_path cmd) args) 78 let closeProcessArgsFull = fullProcRes.release 79 80 let readChannelTillEof c = 81 let lst = ref [] in 82 let rec loop () = 83 lst := input_line c :: !lst; 84 loop () 85 in 86 begin try loop () with End_of_file -> () end; 87 String.concat "\n" (Safelist.rev !lst) 88 89 let readChannelTillEof_lwt c = 90 let rec loop lines = 91 Lwt.try_bind 92 (fun () -> Lwt_unix.input_line c) 93 (fun l -> loop (l :: lines)) 94 (fun e -> if e = End_of_file then Lwt.return lines else Lwt.fail e) 95 in 96 String.concat "\n" (Safelist.rev (Lwt_unix.run (loop []))) 97 98 let readChannelsTillEof l = 99 let rec suckitdry lines c = 100 Lwt.try_bind 101 (fun () -> Lwt_unix.input_line c) 102 (fun l -> suckitdry (l :: lines) c) 103 (fun e -> match e with End_of_file -> Lwt.return lines | _ -> raise e) 104 in 105 Lwt_util.map 106 (fun c -> 107 suckitdry [] c 108 >>= (fun res -> return (String.concat "\n" (Safelist.rev res)))) 109 l 110 111 112 let runExternalProgramAux ~winProc ~posixProc = 113 if Sys.win32 then begin 114 debug (fun()-> Util.msg "Executing external program windows-style\n"); 115 let c = winProc () in 116 let log = Util.trimWhitespace (readChannelTillEof c) in 117 let returnValue = closeProcessIn c in 118 let resultLog = 119 (*cmd ^ 120 (if log <> "" then "\n\n" ^*) log (*else "")*) ^ 121 (if returnValue <> Unix.WEXITED 0 then 122 "\n\n" ^ Util.process_status_to_string returnValue 123 else 124 "") in 125 Lwt.return (returnValue, resultLog) 126 end else 127 let (out, ipt, err) as desc = posixProc () in 128 let out = Lwt_unix.intern_in_channel out in 129 let err = Lwt_unix.intern_in_channel err in 130 readChannelsTillEof [out;err] 131 >>= (function [logOut;logErr] -> 132 let returnValue = closeProcessFull desc in 133 let logOut = Util.trimWhitespace logOut in 134 let logErr = Util.trimWhitespace logErr in 135 return (returnValue, ( 136 (* cmd 137 ^ "\n\n" ^ *) 138 (if logOut = "" || logErr = "" 139 then logOut ^ logErr 140 else logOut ^ "\n\n" ^ ("Error Output:" ^ logErr)) 141 ^ (if returnValue = Unix.WEXITED 0 142 then "" 143 else "\n\n" ^ Util.process_status_to_string returnValue))) 144 (* Stop typechechecker from complaining about non-exhaustive pattern above *) 145 | _ -> assert false) 146 147 let runExternalProgram cmd = 148 runExternalProgramAux 149 ~winProc:(fun () -> openProcessIn ("\"" ^ cmd ^ "\"")) 150 ~posixProc:(fun () -> openProcessFull cmd) 151 152 let runExternalProgramArgs cmd args = 153 runExternalProgramAux 154 ~winProc:(fun () -> openProcessArgsIn cmd args) 155 ~posixProc:(fun () -> openProcessArgsFull cmd args)