Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source
Source file command_server.ml
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273openCoreopenAsyncmoduleDef_error=Deferred.Or_errormoduleChannel_IO:sigvalrun_command:Process.t->stringlist->Process.Output.tDef_error.tvalread:Reader.t->[`Messageof[`Error|`Output]*string|`Resultofint]Def_error.tend=structletread_lengthchild_stdout=letbuf=Bytes.create4inReader.really_readchild_stdoutbuf>>|function|`Eoflen->Or_error.error"read_length: eof"(Bytes.To_string.sub~pos:0~lenbuf)String.sexp_of_t|`Ok->Ok(Binary_packing.unpack_unsigned_32_int_big_endian~buf~pos:0);;letreadchild_stdout=Reader.read_charchild_stdout>>=function|`Eof->Deferred.Or_error.error_string"unexpected eof while reading channel"|`Okchannel_char->letchannel=matchchannel_charwith|'o'->Ok`Output|'e'->Ok`Error|'r'->Ok`Result(* these are part of the spec, but unsupported by this implementation *)(* | 'I' | 'L' | 'd' *)|_->Or_error.error"unsupported channel"channel_charChar.sexp_of_tin(matchchannelwith|Error_aserr->returnerr|Okchannel->read_lengthchild_stdout>>=?funlen->letbuf=Bytes.createleninReader.really_readchild_stdoutbuf>>|(function|`Eoflen->Or_error.error"eof while reading message"(channel,len,Bytes.To_string.subbuf~pos:0~len)[%sexp_of:[`Output|`Error|`Result]*int*string]|`Ok->(matchchannelwith|(`Output|`Error)aschannel->Ok(`Message(channel,Bytes.to_stringbuf))|`Result->Ok(`Result(Binary_packing.unpack_signed_32_int_big_endian~buf~pos:0)))));;letread_fullchild_stdout=letflattenoutputs=letstdouts,stderrs=List.partition_mapoutputs~f:(fun(channel,text)->matchchannelwith|`Output->Firsttext|`Error->Secondtext)inString.concatstdouts,String.concatstderrsinletrecloopacc=readchild_stdout>>=?function|`Message(channel,text)->loop((channel,text)::acc)|`Resultexit_code->letstdout,stderr=flatten(List.revacc)inletexit_status=ifexit_code=0thenOk()elseError(`Exit_non_zeroexit_code)inDef_error.return{Process.Output.stdout;stderr;exit_status}inloop[];;letsend_commandchild_stdinargs=letcommand=String.concatargs~sep:"\000"inletbuf=Bytes.create4inBinary_packing.pack_unsigned_32_int_big_endian~buf~pos:0(String.lengthcommand);try_with~run:`Schedule(* consider [~run:`Now] instead; see: https://wiki/x/ByVWF *)~rest:`Log(* consider [`Raise] instead; see: https://wiki/x/Ux4xF *)(fun()->Writer.writechild_stdin"runcommand\n";Writer.write_byteschild_stdinbuf;Writer.writechild_stdincommand;Writer.flushedchild_stdin)>>|function|Ok_asok->ok|Errorexn->Or_error.error"unable to write command; process is probably dead!"(args,exn)[%sexp_of:stringlist*exn];;letrun_commandprocessargs=letchild_stdin=Process.stdinprocessinsend_commandchild_stdinargs>>=?fun()->read_full(Process.stdoutprocess);;endtypet=Process.tThrottle.Sequencer.tletvalid_hello~accepted_encodingshello=letaccepted_encodings=List.mapaccepted_encodings~f:(function|`Ascii->"ascii"|`Utf8->"UTF-8")inletattrs=List.filter_map(String.split~on:'\n'hello)~f:(funline->Option.map(String.lsplit2~on:':'line)~f:(fun(name,data)->String.stripname,String.stripdata))inletcheckkey~f=matchList.Assoc.findattrs~equal:String.equalkeywith|None->Or_error.error_s[%message"key not in attrs"(key:string)(attrs:(string*string)list)]|Somevalue->fvalueinOr_error.combine_errors_unit[check"capabilities"~f:(funvals->letcapabilities=String.split~on:' 'valsinletis_runcommandvalue=String.equal"runcommand"(String.stripvalue)inifList.existscapabilities~f:is_runcommandthenOk()elseOr_error.error_s[%message"capabilities don't include runcommand"(capabilities:stringlist)]);check"encoding"~f:(funencoding->ifList.mem~equal:String.equalaccepted_encodingsencodingthenOk()elseOr_error.error_s[%message"encoding unacceptable; this can be caused by incorrect locale settings, \
check the output of the `locale` command"(accepted_encodings:stringlist)(encoding:string)])];;let%test_=Result.is_ok(valid_hello~accepted_encodings:[`Utf8]"capabilities: getencoding runcommand\nencoding: UTF-8");;let%test_=Result.is_ok(valid_hello~accepted_encodings:[`Ascii]"capabilities: getencoding runcommand\nencoding: ascii\n");;let%test_=Result.is_error(valid_hello~accepted_encodings:[`Utf8]"capabilities: getencoding runcommand\nencoding: ascii\n");;let%test_=Result.is_error(valid_hello~accepted_encodings:[`Ascii]"capabilities: getencoding runcommand\nencoding: UTF-8");;let%test_=Result.is_error(valid_hello~accepted_encodings:[`Ascii]"capabilities: getencoding\nencoding: ascii\n");;let%expect_test"report both errors"=letopenExpect_test_helpers_coreinshow_raise(fun()->valid_hello~accepted_encodings:[`Utf8]"capabilities: getencoding\nencoding: ascii\n"|>ok_exn);[%expect{|
(raised (
("capabilities don't include runcommand" (capabilities (getencoding)))
("encoding unacceptable; this can be caused by incorrect locale settings, check the output of the `locale` command"
(accepted_encodings (UTF-8))
(encoding ascii))))
|}];return();;moduleSsh=structtypet={host:string;user:stringoption;options:stringlist}endletcreate?env?(hg_binary="hg")?config~accepted_encodingsssh=letconfig=Option.value_mapconfig~default:[]~f:(funconfig->List.concat_mapconfig~f:(fun(key,data)->["--config";key^"="^data]))inletprog,extra_args=matchsshwith|None->hg_binary,[]|Some{Ssh.host;user;options}->letuser_string=matchuserwith|None->""|Someuser->user^"@"in"ssh",options@[user_string^host;"--";hg_binary]inletargs=extra_args@["serve";"--cmdserver";"pipe"]@configin(matchsshwith|None->(* When running a local server, start it in the user's home directory. This makes it
consistent with running a remote server. *)Monitor.try_with_or_error~here:[%here]Sys.home_directory>>|?Option.return|Some_->return(OkNone))>>=?funworking_dir->Process.create?env?working_dir~prog~args()>>=?funprocess->lethello_result=Channel_IO.read(Process.stdoutprocess)>>=?function|`Message(`Error,error)->Deferred.Or_error.error_s[%message"replied on error channel"(error:string)]|`Resultresult->Deferred.Or_error.error_s[%message"replied on result channel, expecting output channel"(result:int)]|`Message(`Output,text)->return(valid_hello~accepted_encodingstext)inDeferred.Or_error.tag_arghello_result"parsing hello from command server failed"(prog,args)[%sexp_of:string*stringlist]>>=function|Ok()->Deferred.Or_error.return(Throttle.Sequencer.createprocess)|Error_aserr->Process.send_signalprocessSignal.term;Process.collect_output_and_waitprocess>>|funoutput->Or_error.tag_argerr"Process output"outputProcess.Output.sexp_of_t;;letrun_commandt~cwdargs=Throttle.enqueuet(funprocess->Channel_IO.run_commandprocess("--cwd"::cwd::args));;letdestroyt=Throttle.enqueuet(funprocess->Deferred.ignore_m(Process.collect_output_and_waitprocess));;