123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304(**
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the "hack" directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*)type'ain_channel=Timeout.in_channeltype'aout_channel=Pervasives.out_channeltype('in_,'out)channel_pair='in_in_channel*'outout_channeltype('in_,'out)handle={channels:('in_,'out)channel_pair;pid:int;}(* Windows: ensure that the serialize/deserialize functions
for the custom block of "Unix.file_descr" are registred. *)let()=Lazy.forceHandle.initletto_channel:'aout_channel->?flags:Marshal.extern_flagslist->?flush:bool->'a->unit=funoc?(flags=[])?flush:(should_flush=true)v->Marshal.to_channelocvflags;ifshould_flushthenflushocletfrom_channel:?timeout:Timeout.t->'ain_channel->'a=fun?timeoutic->Timeout.input_value?timeouticletflush:'aout_channel->unit=Pervasives.flushletdescr_of_in_channel:'ain_channel->Unix.file_descr=Timeout.descr_of_in_channelletdescr_of_out_channel:'aout_channel->Unix.file_descr=Unix.descr_of_out_channelletcast_inic=icletcast_outoc=oc(* We cannot fork() on Windows, so in order to emulate this in a
* cross-platform way, we use create_process() and set the HH_SERVER_DAEMON
* environment variable to indicate which function the child should
* execute. On Unix, create_process() does fork + exec, so global state is
* not copied; in particular, if you have set a mutable reference the
* daemon will not see it. All state must be explicitly passed via
* environment variables; see set/get_context() below.
*
* With some factoring we could make the daemons into separate binaries
* altogether and dispense with this emulation. *)moduleEntry:sig(* All the 'untyped' operations---that are required for the
entry-points hashtable and the parameters stored in env
variable---are hidden in this sub-module, behind a 'type-safe'
interface. *)type('param,'input,'output)tvalname_of_entry:('param,'input,'output)t->stringvalregister:string->('param->('input,'output)channel_pair->unit)->('param,'input,'output)tvalfind:('param,'input,'output)t->'param->('input,'output)channel_pair->unitvalset_context:('param,'input,'output)t->'param->Unix.file_descr*Unix.file_descr->unitvalget_context:unit->(('param,'input,'output)t*'param*('input,'output)channel_pair)valclear_context:unit->unitend=structtype('param,'input,'output)t=stringletname_of_entryname=name(* Store functions as 'Obj.t' *)letentry_points:(string,Obj.t)Hashtbl.t=Hashtbl.create23letregisternamef=ifHashtbl.mementry_pointsnamethenPrintf.ksprintffailwith"Daemon.register_entry_point: duplicate entry point %S."name;Hashtbl.addentry_pointsname(Obj.reprf);nameletfindname=tryObj.obj(Hashtbl.findentry_pointsname)withNot_found->Printf.ksprintffailwith"Unknown entry point %S"nameletset_contextentryparam(ic,oc)=letdata=(ic,oc,param)inUnix.putenv"HH_SERVER_DAEMON"entry;letfile,oc=Filename.open_temp_file~mode:[Open_binary]~temp_dir:Sys_utils.temp_dir_name"daemon_param"".bin"inoutput_valueocdata;close_outoc;Unix.putenv"HH_SERVER_DAEMON_PARAM"file(* How this works on Unix: It may appear like we are passing file descriptors
* from one process to another here, but in_handle / out_handle are actually
* file descriptors that are already open in the current process -- they were
* created by the parent process before it did fork + exec. However, since
* exec causes the child to "forget" everything, we have to pass the numbers
* of these file descriptors as arguments.
*
* I'm not entirely sure what this does on Windows. *)letget_context()=letentry=Unix.getenv"HH_SERVER_DAEMON"inifentry=""thenraiseNot_found;let(in_handle,out_handle,param)=tryletfile=Sys.getenv"HH_SERVER_DAEMON_PARAM"iniffile=""thenraiseNot_found;letic=Sys_utils.open_in_bin_no_failfileinletres=Marshal.from_channelicinSys_utils.close_in_no_fail"Daemon.get_context"ic;Sys.removefile;reswith_exn->failwith"Can't find daemon parameters."in(entry,param,(Timeout.in_channel_of_descrin_handle,Unix.out_channel_of_descrout_handle))letclear_context()=Unix.putenv"HH_SERVER_DAEMON""";Unix.putenv"HH_SERVER_DAEMON_PARAM""";endtype('param,'input,'output)entry=('param,'input,'output)Entry.tletexecentryparamicoc=letf=Entry.findentryintryfparam(ic,oc);exit0withe->prerr_endline(Printexc.to_stringe);Printexc.print_backtracestderr;exit2letregister_entry_point=Entry.registerletfd_of_pathpath=Sys_utils.with_umask0o111beginfun()->Sys_utils.mkdir_no_fail(Filename.dirnamepath);Unix.openfilepath[Unix.O_RDWR;Unix.O_CREAT;Unix.O_TRUNC]0o666endletnull_fd()=fd_of_pathSys_utils.null_pathletsetup_channelschannel_mode=matchchannel_modewith|`pipe->letparent_in,child_out=Unix.pipe()inletchild_in,parent_out=Unix.pipe()in(* Close descriptors on exec so they are not leaked. *)Unix.set_close_on_execparent_in;Unix.set_close_on_execparent_out;(parent_in,child_out),(child_in,parent_out)|`socket->letparent_fd,child_fd=Unix.socketpairUnix.PF_UNIXUnix.SOCK_STREAM0in(* FD's on sockets are bi-directional. *)(parent_fd,child_fd),(child_fd,parent_fd)letmake_pipe(descr_in,descr_out)=letic=Timeout.in_channel_of_descrdescr_ininletoc=Unix.out_channel_of_descrdescr_outinic,ocletclose_pipechannel_mode(ch_in,ch_out)=matchchannel_modewith|`pipe->Timeout.close_inch_in;close_outch_out|`socket->(* the in and out FD's are the same. Close only once. *)Timeout.close_inch_in(* This only works on Unix, and should be avoided as far as possible. Use
* Daemon.spawn instead. *)letfork?(channel_mode=`pipe)(typeparam)(log_stdout,log_stderr)(f:param->('a,'b)channel_pair->unit)(param:param):('b,'a)handle=let(parent_in,child_out),(child_in,parent_out)=setup_channelschannel_modeinlet(parent_in,child_out)=make_pipe(parent_in,child_out)inlet(child_in,parent_out)=make_pipe(child_in,parent_out)inmatchFork.fork()with|-1->failwith"Go get yourself a real computer"|0->(* child *)(tryignore(Unix.setsid());close_pipechannel_mode(parent_in,parent_out);Sys_utils.with_umask0o111beginfun()->letfd=null_fd()inUnix.dup2fdUnix.stdin;Unix.closefd;end;Unix.dup2log_stdoutUnix.stdout;Unix.dup2log_stderrUnix.stderr;iflog_stdout<>Unix.stdoutthenUnix.closelog_stdout;iflog_stderr<>Unix.stderr&&log_stderr<>log_stdoutthenUnix.closelog_stderr;fparam(child_in,child_out);exit0withe->prerr_endline(Printexc.to_stringe);Printexc.print_backtracestderr;exit1)|pid->(* parent *)close_pipechannel_mode(child_in,child_out);{channels=parent_in,parent_out;pid}letspawn(typeparam)(typeinput)(typeoutput)?(channel_mode=`pipe)(stdin,stdout,stderr)(entry:(param,input,output)entry)(param:param):(output,input)handle=let(parent_in,child_out),(child_in,parent_out)=setup_channelschannel_modeinEntry.set_contextentryparam(child_in,child_out);letexe=Sys_utils.executable_path()inletpid=Unix.create_processexe[|exe|]stdinstdoutstderrinEntry.clear_context();(matchchannel_modewith|`pipe->Unix.closechild_in;Unix.closechild_out;|`socket->(* the in and out FD's are the same. Close only once. *)Unix.closechild_in);ifstdin<>Unix.stdinthenUnix.closestdin;ifstdout<>Unix.stdoutthenUnix.closestdout;ifstderr<>Unix.stderr&&stderr<>stdoutthenUnix.closestderr;PidLog.log~reason:(Entry.name_of_entryentry)~no_fail:truepid;{channels=Timeout.in_channel_of_descrparent_in,Unix.out_channel_of_descrparent_out;pid}(* for testing code *)letdevnull()=letic=Timeout.open_in"/dev/null"inletoc=open_out"/dev/null"in{channels=ic,oc;pid=0}(**
* In order for the Daemon infrastructure to work, the beginning of your
* program (or very close to the beginning) must start with a call to
* check_entry_point.
*
* Details: Daemon.spawn essentially does a fork then exec of the currently
* running program. Thus, the child process will just end up running the exact
* same program as the parent if you forgot to start with a check_entry_point.
* The parent process sees this as a NOOP when its program starts, but a
* child process (from Daemon.spawn) will use this as a GOTO to its entry
* point.
*)letcheck_entry_point()=tryletentry,param,(ic,oc)=Entry.get_context()inEntry.clear_context();execentryparamicocwithNot_found->()letclose{channels=(ic,oc);_}=Timeout.close_inic;close_outocletkillh=closeh;Sys_utils.terminate_processh.pidletclose_out=close_outletoutput_string=output_stringletflush=flushletclose_in=Timeout.close_inletinput_charic=Timeout.input_charicletinput_valueic=Timeout.input_valueic