unison

Fork of Unison, a bi-directional file synchronization tool
git clone git://git.laack.co/unison.git
Log | Files | Refs | README | LICENSE

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)