123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154open!CoreopenPolyopen!ImportmoduleUnix=Core_unixletcheck_threads~allow_threads_to_have_been_created=(* forking, especially to daemonize, when running multiple threads is tricky, and
generally a mistake. It's so bad, and so hard to catch, that we test in two
different ways *)ifnotallow_threads_to_have_been_created&&Thread.threads_have_been_created()thenfailwith"Daemon.check_threads: may not be called \
if any threads have ever been created";beginmatchThread.num_threads()with|None->()(* This is pretty bad, but more likely to be a problem with num_threads *)|Some(1|2)->()(* main thread, or main + ticker - both ok *)|Some_->failwith"Daemon.check_threads: may not be called if more than 2 threads \
(hopefully the main thread + ticker thread) are running"end;;;moduleFd_redirection=structtypedo_redirect=[`Dev_null|`Dev_null_skip_regular_files|`File_appendofstring|`File_truncateofstring]typet=[`Do_not_redirect|do_redirect]end;;letredirect_fd?perm~mode~src~dst()=matchsrcwith|`Do_not_redirect->()|#Fd_redirection.do_redirectassrc->letredirectsrc=Unix.dup2~src~dst();Unix.closesrc;inletopen_dev_null()=Unix.openfile"/dev/null"~mode:[mode]~perm:0o777inmatchsrcwith|`Dev_null_skip_regular_files->letis_regular()=try(Unix.fstatdst).Unix.st_kind=Unix.S_REGwithUnix.Unix_error(EBADF,_,_)->falseinifnot(is_regular())thenredirect(open_dev_null())else()|`Dev_null->redirect(open_dev_null())|`File_appendfile->redirect(Unix.openfilefile?perm~mode:[mode;Unix.O_CREAT;Unix.O_APPEND])|`File_truncatefile->redirect(Unix.openfilefile?perm~mode:[mode;Unix.O_CREAT;Unix.O_TRUNC]);;letredirect_stdio_fds?perm~stdout~stderr()=redirect_fd?perm~mode:Unix.O_RDONLY~src:`Dev_null~dst:Unix.stdin();redirect_fd?perm~mode:Unix.O_WRONLY~src:stdout~dst:Unix.stdout();redirect_fd?perm~mode:Unix.O_WRONLY~src:stderr~dst:Unix.stderr();;;letdaemonize?(redirect_stdout=`Dev_null)?(redirect_stderr=`Dev_null)?(cd="/")?perm?umask?(allow_threads_to_have_been_created=false)()=check_threads~allow_threads_to_have_been_created;letfork_no_parent()=matchUnix.handle_unix_errorUnix.forkwith|`In_the_child->()|`In_the_parent_->exit0in(* Fork into the background, parent exits, child continues. *)fork_no_parent();(* Become session leader. *)ignore(Unix.Terminal_io.setsid());(* Fork again to ensure that we will never regain a controlling terminal. *)fork_no_parent();(* Release old working directory. *)Unix.chdircd;(* Ensure sensible umask. Adjust as needed. *)Option.iterumask~f:(funumask->ignore(Unix.umaskumask));redirect_stdio_fds?perm~stdout:redirect_stdout~stderr:redirect_stderr();;;letprocess_status_to_exit_code=function|Ok()->0|Error(`Exit_non_zeroi)->i|Error(`Signals)->(* looking at byterun/signals.c in ocaml source tree, I think this should never be
zero for signals coming from [wait*] function family. *)Signal.to_caml_intsletdaemonize_wait?(redirect_stdout=`Dev_null_skip_regular_files)?(redirect_stderr=`Dev_null_skip_regular_files)?(cd="/")?perm?umask?(allow_threads_to_have_been_created=false)()=check_threads~allow_threads_to_have_been_created;matchUnix.handle_unix_errorUnix.forkwith|`In_the_child->ignore(Unix.Terminal_io.setsid());letread_end,write_end=Unix.pipe()inletbuf="done"inletlen=String.lengthbufinbeginmatchUnix.handle_unix_errorUnix.forkwith|`In_the_child->(* The process that will become the actual daemon. *)Unix.closeread_end;Unix.chdircd;Option.iterumask~f:(funumask->ignore(Unix.umaskumask));Staged.stage(fun()->redirect_stdio_fds?perm~stdout:redirect_stdout~stderr:redirect_stderr();letold_sigpipe_behavior=Signal.Expert.signalSignal.pipe`Ignorein(tryignore(Unix.write_substringwrite_end~buf~pos:0~len:int)with_->());Signal.Expert.setSignal.pipeold_sigpipe_behavior;Unix.closewrite_end)|`In_the_parentpid->(* The middle process, after it has forked its child. *)Unix.closewrite_end;letrecloop()=matchUnix.wait_nohang(`Pidpid)with|None->beginmatchUnix.select~read:[read_end]~write:[]~except:[]~timeout:(`After(Time_ns.Span.of_sec0.1))()with|{Unix.Select_fds.read=[read_end];write=[];except=[]}->(* If the child process exits before detaching and the middle process
happens to be in this call to select, the pipe will be closed and select
will return a ready file descriptor, but with zero bytes to read.
In this case, we want to loop back again and call waitpid to obtain
the correct exit status to propagate on to the outermost parent
(otherwise we might incorrectly return a success). *)ifUnix.readread_end~buf:(Bytes.createlen)~pos:0~len>0thenexit0elseloop()|_->loop()end|Some(_pid,process_status)->exit(process_status_to_exit_codeprocess_status)inloop()end|`In_the_parentpid->exit(process_status_to_exit_code(Unix.waitpidpid));;