123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494(* TODO: Ron wants the ability to run interactive commands and to expose the fd
version of process handling.*)openCoremoduleLine_buffer=Shell__line_bufferletextra_path=Shell_internal.extra_pathmoduleProcess=structexceptionEarly_exit[@@derivingsexp]typestatus=[`TimeoutofTime.Span.t|Low_level_process.Status.t][@@derivingsexp_of](* type status = (unit, error) Result.t with sexp_of *)typet={program:string;arguments:stringlist;}[@@derivingsexp_of]typeresult={command:t;status:status;stdout:string;stderr:string;}[@@derivingsexp_of]exceptionFailedofresult[@@derivingsexp]letto_string{program=prog;arguments=args}=letfs=ifnot(String.containss' ')&¬(String.containss'"')thenselsesprintf"%S"sinString.concat~sep:" "(List.map~f(prog::args))letstatus_to_string=function|`Timeoutt->sprintf!"Timed out (ran for %{Time.Span})"t|#Low_level_process.Status.tass->Low_level_process.Status.to_stringsletformat_failedc=String.concat~sep:" "["Command failed:";to_stringc.command;"Exit status:";status_to_stringc.status;"stderr:";c.stderr]let()=Caml.Printexc.register_printer(function|Failedr->Some(format_failedr)|_->None)moduleDefaults=structlettimeout=refNoneletverbose=reffalseletecho=reffalseletpreserve_euid=reffalseendletset_defaults?timeout?verbose?echo?preserve_euid()=Option.iter~f:(funv->Defaults.verbose:=v)verbose;Option.iter~f:(funv->Defaults.timeout:=v)timeout;Option.iter~f:(funv->Defaults.echo:=v)echo;Option.iter~f:(funv->Defaults.preserve_euid:=v)preserve_euidletcmdprogramarguments={program=program;arguments=arguments;}letshells=letaddtl_args=if!Defaults.preserve_euidthen["-p"]else[]in{program="/bin/bash";arguments=addtl_args@["-c";s]}(* avoid asking for the password at all costs. *)letnoninteractive_ssh_options=["-o";"BatchMode yes"]letnoninteractive_no_hostkey_checking_options=["-n";"-q";"-x";"-o";"ConnectTimeout=10";"-o";"CheckHostIP=no";"-o";"StrictHostKeyChecking=no";"-o";"BatchMode=yes";](* Passes the remote command to ssh *)letmake_ssh_command?(ssh_options=noninteractive_ssh_options)?(quote_args=true)?user~hostargs=(* quote_args quotes all arguments to the shell. We need to escape all the
arguments because ssh is passing this to the remote shell which will
unescape all of that before passing it over to our program.*)leturl=matchuserwith|None->host|Someuser->user^"@"^hostinletargs=ifquote_argsthenList.map~f:Filename.quoteargselseargsin{program="/usr/bin/ssh";arguments=ssh_options@[url;"--"]@args;}letremote?ssh_options?quote_args?user~hostcmd=make_ssh_command?ssh_options?quote_args?user~host(cmd.program::cmd.arguments)type'resacc={add_stdout:Bytes.t->int->[`Stop|`Continue];add_stderr:Bytes.t->int->[`Stop|`Continue];flush:unit->'res;}type'resreader=unit->'resaccletrun_k'k?use_extra_path?(timeout=!Defaults.timeout)?working_dir?setuid?setgid?env?(verbose=!Defaults.verbose)?(echo=!Defaults.echo)?input?keep_open?tail_len=k(funcmdstdoutfstderrf->ifechothenConsole.Ansi.printf[`Underscore]!"Shell: %{}\n%!"cmd;letstderrf=ifverbosethen(funslen->Console.Ansi.output[`Red]stderrs0len)elsestderrfandstdoutf=ifverbosethen(funslen->Console.Ansi.output[`Green]stdouts0len;stdoutfslen)elsestdoutfin(Low_level_process.run?timeout?input?keep_open?working_dir?setuid?setgid?use_extra_path?env?tail_len~stdoutf~stderrf~prog:cmd.program~args:cmd.arguments()))letrun_kk?(expect=[0])=run_k'(funf->k(funcmdreader->letacc=reader()inletstdoutfslen=matchacc.add_stdoutslenwith|`Continue->()|`Stop->raiseEarly_exitinletstderrfslen=matchacc.add_stderrslenwith|`Continue->()|`Stop->raiseEarly_exitintryletr=fcmdstdoutfstderrfinletmoduleRes=Low_level_process.Command_resultinmatchr.Res.statuswith|`ExitediwhenList.memexpecti~equal:Int.equal->acc.flush()|status->raise(Failed{command=cmd;status=(status:>status);stderr=r.Res.stderr_tail;stdout=r.Res.stdout_tail;})withEarly_exit->acc.flush()))letrun?expect=run_k(funfcmdreader->fcmdreader)?expectlettest_kk?(true_v=[0])?(false_v=[1])=run_k'(funf->k(funcmd->letr=fcmd(fun__->())(fun__->())inletmoduleRes=Low_level_process.Command_resultinmatchr.Res.statuswith|`ExitediwhenList.memtrue_vi~equal:Int.equal->true|`ExitediwhenList.memfalse_vi~equal:Int.equal->false|#statusasstatus->raise(Failed{command=cmd;status=(status:>status);stderr=r.Res.stderr_tail;stdout=r.Res.stdout_tail})))lettest?true_v=test_k(funfcmd->fcmd)?true_vletdiscard()={add_stdout=(fun__->`Continue);add_stderr=(fun__->`Continue);flush=(fun()->())}letcallback~add~flush()={add_stdout=(funslen->addslen;`Continue);add_stderr=(fun__->`Continue);flush}letcallback_with_stderr~add~add_err~flush()={add_stdout=(funslen->addslen;`Continue);add_stderr=(funslen->add_errslen;`Continue);flush}letcontent()=letbuffer=Buffer.create16in{add_stdout=(funslen->Buffer.add_subbytesbuffers~pos:0~len;`Continue);add_stderr=(fun__->`Continue);flush=(fun()->Buffer.contentsbuffer);}letcontent_and_stderr()=letstdout_buffer=Buffer.create16inletbuffer_stderr=Buffer.create16in{add_stdout=(funslen->Buffer.add_subbytesstdout_buffers~pos:0~len;`Continue);add_stderr=(funslen->Buffer.add_subbytesbuffer_stderrs~pos:0~len;`Continue);flush=(fun()->Buffer.contentsstdout_buffer,Buffer.contentsbuffer_stderr);}letfold_lines(typeret)(typev)?eol~(init:v)~(f:v->string->(v*[`Continue|`Stop]))~(flush:v->ret)():retacc=letacc=refinitandcontinue=ref`Continueinletlb=Line_buffer.create?eol(funline->match!continuewith|`Stop->()|`Continue->letacc_v,continue_v=f!acclineinacc:=acc_v;continue:=continue_v)in{add_stdout=(funslen->Line_buffer.add_subbyteslbs~pos:0~len;!continue);add_stderr=(fun__->`Continue);flush=(fun()->Line_buffer.flushlb;flush!acc)}letlines?eol()=fold_lines?eol~flush:List.rev~init:[]~f:(funaccline->(line::acc),`Continue)letaux_head~flush?eol()=fold_lines?eol~flush~init:None~f:(fun_accline->Someline,`Stop)lethead?eol()=aux_head~flush:(funx->x)?eol()exceptionEmpty_headlethead_exn?eol()=aux_head~flush:(functionSomex->x|None->raiseEmpty_head)?eol()letaux_one_line~flush?eol()=fold_lines?eol~flush:(function|Someresult->flushresult|None->flush(Or_error.error_s[%message"expected one line, got empty output"]))~init:None~f:(funaccline->matchaccwith|Some(Okfirst_line)->letsecond_line=lineinSome(Or_error.error_s[%message"One line expected, got at least two lines of output"~first_line~second_line]),`Stop|Some(Error_e)->(* didn't we say `Stop?! *)assertfalse|None->Some(Okline),`Continue)letone_line_exn?eol()=aux_one_line~flush:Or_error.ok_exn?eol()letone_line?eol()=aux_one_line~flush:Fn.id?eol()endlet%test_unit_=[%test_result:string]~expect:"hello"(Process.run(Process.cmd"echo"["hello\nworld"])(Process.head_exn()))type'awith_process_flags=?use_extra_path:bool->?timeout:Time.Span.toption->?working_dir:string(* rename to run_in? *)->?setuid:int->?setgid:int->?env:[`Extendof(string*string)list|`Replaceof(string*string)list]->?verbose:bool->?echo:bool->?input:string->?keep_open:bool->?tail_len:int->'atype'awith_run_flags=(* Defaults to [0]*)?expect:intlist->('awith_process_flags)type'awith_test_flags=?true_v:intlist->?false_v:intlist->('awith_process_flags)type'acmd=string->stringlist->'atype('a,'ret)sh_cmd=(('a,unit,string,'ret)format4->'a)letrun_genreader=Process.run_k(funfprogargs->f(Process.cmdprogargs)reader)letrun=run_genProcess.discardletrun_lines?eol=run_gen(Process.lines?eol())letrun_one?eol=run_gen(Process.head?eol())letrun_one_exn?eol=run_gen(Process.head_exn?eol())letrun_first_line?eol=run_gen(Process.head?eol())letrun_first_line_exn?eol=run_gen(Process.head_exn?eol())letrun_one_line?eol=run_gen(Process.one_line?eol())letrun_one_line_exn?eol=run_gen(Process.one_line_exn?eol())letrun_full=run_genProcess.contentletrun_fold?eol~init~f=run_gen(Process.fold_lines?eol~init~f~flush:Fn.id)(*
TEST_UNIT =
(* This should not hand because the stdin is closed... *)
run ~timeout:(Some (sec 0.5)) "cat" []
TEST_UNIT =
try
run ~timeout:(Some (sec 0.5)) "cat" []
with Process.
*)lettest=Process.test_k(funfprogargs->f(Process.cmdprogargs))letk_shell_commandkffmt=ksprintf(funcommand->kf(Process.shellcommand))fmtletsh_genreader=Process.run_k(k_shell_command(funfcmd->fcmdreader))letsh?expect=sh_genProcess.discard?expectletsh_lines?expect=sh_gen(Process.lines())?expectletsh_full?expect=sh_genProcess.content?expectletsh_one?expect=sh_gen(Process.head())?expectletsh_one_exn?expect=sh_gen(Process.head_exn())?expectletsh_first_line?expect=sh_gen(Process.head())?expectletsh_first_line_exn?expect=sh_gen(Process.head_exn())?expectletsh_one_line?expect=sh_gen(Process.one_line())?expectletsh_one_line_exn?expect=sh_gen(Process.one_line_exn())?expectlet%test_=sh_lines"yes yes | head -n 200000"=List.init200_000~f:(fun_num->"yes")letsh_test?true_v=Process.test_k(k_shell_command(funfcmd->fcmd))?true_vtype'awith_ssh_flags=?ssh_options:stringlist->?user:string->host:string->'aletnoninteractive_ssh_options=Process.noninteractive_ssh_optionsletnoninteractive_no_hostkey_checking_options=Process.noninteractive_no_hostkey_checking_optionsletk_remote_commandkf?ssh_options?user~hostfmt=ksprintf(funcommand->kf(Process.make_ssh_command~quote_args:false?ssh_options?user~host[command]))fmtletssh_genreader?ssh_options?user~host=Process.run_k(k_remote_command(funfcmd->fcmdreader)?ssh_options?user~host)letssh?ssh_options=ssh_genProcess.discard?ssh_optionsletssh_lines?ssh_options=ssh_gen(Process.lines())?ssh_optionsletssh_full?ssh_options=ssh_genProcess.content?ssh_optionsletssh_one?ssh_options=ssh_gen(Process.head())?ssh_optionsletssh_one_exn?ssh_options=ssh_gen(Process.head_exn())?ssh_optionsletssh_first_line?ssh_options=ssh_gen(Process.head())?ssh_optionsletssh_first_line_exn?ssh_options=ssh_gen(Process.head_exn())?ssh_optionsletssh_one_line?ssh_options=ssh_gen(Process.one_line())?ssh_optionsletssh_one_line_exn?ssh_options=ssh_gen(Process.one_line_exn())?ssh_optionsletssh_test?ssh_options?user~host=Process.test_k(k_remote_command(funfcmd->fcmd)?ssh_options?user~host)letwhoami=Shell_internal.whoamiletwhich=Shell_internal.whichletln?s?fsrcdst=lets=Option.maps~f:(fun()->"-s")inletf=Option.mapf~f:(fun()->"-f")inrun"/bin/ln"(List.filter_map~f:ident[s;f]@["-n";"--";src;dst])letrm?r?fpath=letr=Option.mapr~f:(fun()->"-r")inletf=Option.mapf~f:(fun()->"-f")inrun"/bin/rm"(List.filter_map~f:ident[r;f;Some"--";Somepath])letmvsrcdst=run"/bin/mv"["--";src;dst]letmkdir?p?permpath=letp=Option.mapp~f:(fun()->"-p")inletmode=Option.mapperm~f:(sprintf"--mode=%o")inrun"/bin/mkdir"(List.filter_map~f:ident[p;mode;Some"--";Somepath])(* TODO: Deal with atomicity *)letcp?(overwrite=true)?permsrcdst=letperm=matchpermwith|Somep->p|None->(Unix.lstatsrc).Unix.st_perminletdst=ifSys.is_directorydst=`Yesthendst^/(Filename.basenamesrc)elsedstinletout_mode=ifoverwritethen[Unix.O_WRONLY;Unix.O_NOCTTY;Unix.O_CREAT;Unix.O_TRUNC]else[Unix.O_WRONLY;Unix.O_NOCTTY;Unix.O_CREAT;Unix.O_EXCL]inprotectx(Unix.openfilesrc~mode:[Unix.O_RDONLY;Unix.O_NOCTTY]~perm:0)~f:(funinfh->protectx(Unix.openfiledst~mode:out_mode~perm)~f:(funoutfh->letbuflen=4096inletbuf=Bytes.createbufleninletrecloop()=letrlen=Unix.readinfh~buf~pos:0~len:bufleninifrlen<>0thenletwlen=Unix.writeoutfh~buf~pos:0~len:rleninifrlen<>wlenthenfailwithf"Short write: tried to write %d bytes, \
only wrote %d bytes"rlenwlen();loop()inloop();)~finally:Unix.close)~finally:Unix.close;;letscp?(compress=false)?(recurse=false)?user~hostft=letuser_arg=Option.value_mapuser~default:""~f:(funuser->user^"@")inletargs=[f;user_arg^host^":"^t]inletargs=ifrecursethen"-r"::argselseargsinletargs=ifcompressthen"-C"::argselseargsinrun"scp"args;;