123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115(** daemon utilities *)moduleU=ExtUnix.Specificletlog=Log.from"daemon"letlogfile=refNoneletpidfile=refNoneletrunas=refNoneletforeground=reffalseletmanaged=reffalse(** global flag indicating that process should exit,
[manage] will automatically set this flag on SIGTERM unless default signal handling is overriden
*)letshould_exit_=reffalse(** [should_exit_lwt] usage is discouraged.
Use [wait_exit] instead, which makes it harder to ignore "should exit" state and loop infinitely
*)let(should_exit_lwt,signal_exit_lwt)=Lwt.wait()letshould_exit()=!should_exit_letshould_run()=not!should_exit_(** exception to be raised by functions that wish to signal premature termination due to [!should_exit = true] *)exceptionShouldExitletsignal_exit=letdo_lwt=lazy(Lwt.wakeup_latersignal_exit_lwt())in(* invariant: should_exit_ = (Lwt.state should_exit_lwt = Lwt.Return) *)fun()->should_exit_:=true;Lazy.forcedo_lwt(** @raise ShouldExit if [should_exit] condition is set, otherwise do nothing *)letbreak()=if!should_exit_thenraiseShouldExit(** wait until [should_exit] is set and raise [ShouldExit] *)letwait_exit=(* NOTE
Bind to should_exit_lwt only once, because every bind will create an immutable waiter on
should_exit_lwt's sleeper, that is only removed after should_exit_lwt thread terminates.
*)letthread=lazy(Lwt.bindshould_exit_lwt(fun()->Lwt.failShouldExit))infun()->Lazy.forcethread(** [break_lwt = Lwt.wrap break] *)letbreak_lwt()=Lwt.wrapbreak(** [unless_exit x] resolves promise [x] or raises [ShouldExit] *)letunless_exitx=Lwt.pick[wait_exit();x]letget_args()=[("-loglevel",Arg.StringLog.set_loglevels," ([<facil|prefix*>=]debug|info|warn|error[,])+");ExtArg.may_str"logfile"logfile"<file> Log file";ExtArg.may_str"pidfile"pidfile"<file> PID file";"-runas",Arg.String(funname->tryrunas:=Some(Unix.getpwnamname)withexn->Exn.fail~exn"runas: unknown user %s"name),"<user> run as specified user";"-fg",Arg.Setforeground," Stay in foreground";]letargs=get_args()letinstall_signal_handlers()=letunix_stderrs=lets=Log.State.format_simple`Infolog#facilitysintrylet(_:int)=Unix.write_substringUnix.stderrs0(String.lengths)in()with_->()(* do not fail, can be ENOSPC *)inSignal.set[Sys.sigpipe]ignore;Signal.set_verbose[Sys.sigusr1]"reopen log"(fun()->Log.reopen!logfile);Signal.set_verbose[Sys.sigusr2]"memory reclaim and stats"beginfun()->matchSignal.is_safe_output()with|true->Memory.log_stats();Memory.reclaim()|false->(* output directly to fd to prevent deadlock, but breaks buffering *)Memory.get_stats()|>List.iterunix_stderr;Memory.reclaim_s()|>unix_stderrend;Signal.set_exitsignal_exitletmanage()=match!managedwith|true->()(* be smart *)|false->(*
this will fail if files don't exists :(
(* fail before fork if something is wrong *)
Option.may (fun path -> Unix.(access path [R_OK;W_OK])) !logfile;
Option.may (fun path -> Unix.(access path [R_OK;W_OK])) !pidfile;
*)Option.mayNix.check_pidfile!pidfile;(* check pidfile before fork to fail early *)ifnot!foregroundthenNix.daemonize();beginmatch!runaswith|None->()|Somepw->letuid=pw.Unix.pw_uidandgid=pw.Unix.pw_gidinU.setreuiduiduid;U.setregidgidgid;end;Log.reopen!logfile;(* immediately after fork *)Log.read_env_config();Option.mayNix.manage_pidfile!pidfile;(* write pidfile after fork! *)ifOption.is_some!logfilethenbeginlog#info"run: %s"Nix.cmdline;log#info"GC settings: %s"(Action.gc_settings());end;install_signal_handlers();Nix.raise_limits();managed:=true;()