Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source
Source file low_level.ml
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586openEio.Std(* There are some things that should be improved here:
- Blocking FDs (e.g. stdout) wait for the FD to become ready and then do a blocking operation.
This might not succeed, and will block the whole domain in that case.
Ideally, all blocking operations should happen in a sys-thread instead.
- Various other operations, such as listing a directory, should also be done in a sys-thread
to avoid high latencies in the main domain. *)typety=Read|WritemoduleFd=Eio_unix.FdmoduleTrace=Eio.Private.TracemoduleFiber_context=Eio.Private.Fiber_contexttypedir_fd=|FdofFd.t|Cwd(* Confined to "." *)|Fs(* Unconfined "."; also allows absolute paths *)letin_worker_threadlabel=Eio_unix.run_in_systhread~labelletawait_readableopfd=Fd.use_exn"await_readable"fd@@funfd->Sched.enterop@@funtk->Sched.await_readabletkfdletawait_writableopfd=Fd.use_exn"await_writable"fd@@funfd->Sched.enterop@@funtk->Sched.await_writabletkfdletrecdo_nonblockingtyopfnfd=tryfnfdwith|Unix.Unix_error(EINTR,_,_)->do_nonblockingtyopfnfd(* Just in case *)|Unix.Unix_error((EAGAIN|EWOULDBLOCK),_,_)->Sched.enterop(funtk->matchtywith|Read->Sched.await_readabletkfd|Write->Sched.await_writabletkfd);do_nonblockingtyopfnfdletdo_nonblockingtyopfnfd=Fiber.yield();Trace.with_spanop(fun()->do_nonblockingtyopfnfd)letreadfdbufstartlen=ifFd.is_blockingfdthenawait_readable"read"fd;Fd.use_exn"read"fd@@funfd->do_nonblockingRead"read"(funfd->Unix.readfdbufstartlen)fdletwritefdbufstartlen=ifFd.is_blockingfdthenawait_writable"write"fd;Fd.use_exn"write"fd@@funfd->do_nonblockingWrite"write"(funfd->Unix.writefdbufstartlen)fdletsleep_untiltime=Sched.enter"sleep"@@funtk->Sched.await_timeouttktimeletsocket~swsocket_domainsocket_typeprotocol=Switch.checksw;letsock_unix=Unix.socket~cloexec:truesocket_domainsocket_typeprotocolinUnix.set_nonblocksock_unix;Fd.of_unix~sw~blocking:false~close_unix:truesock_unixletconnectfdaddr=tryFd.use_exn"connect"fd(funfd->Unix.connectfdaddr)with|Unix.Unix_error((EINTR|EAGAIN|EWOULDBLOCK|EINPROGRESS),_,_)->await_writable"connect"fd;matchFd.use_exn"connect"fdUnix.getsockopt_errorwith|None->()|Somecode->raise(Err.wrapcode"connect-in-progress""")letaccept~swsock=Fd.use_exn"accept"sock@@funsock->letclient,addr=do_nonblockingRead"accept"(funfd->Switch.checksw;Unix.accept~cloexec:truefd)sockinUnix.set_nonblockclient;Fd.of_unix~sw~blocking:false~close_unix:trueclient,addrletshutdownsockcmd=Fd.use_exn"shutdown"sock(funfd->Unix.shutdownfdcmd)externaleio_send_msg:Unix.file_descr->int->Unix.file_descrlist->Unix.sockaddroption->Cstruct.tarray->int="caml_eio_posix_send_msg"externaleio_recv_msg:Unix.file_descr->int->Cstruct.tarray->Unix.sockaddr*int*Unix.file_descrlist="caml_eio_posix_recv_msg"letsend_msgfd?(fds=[])?dstbuf=Fd.use_exn"send_msg"fd@@funfd->Fd.use_exn_list"send_msg"fds@@funfds->do_nonblockingWrite"send_msg"(funfd->eio_send_msgfd(List.lengthfds)fdsdstbuf)fdletrecv_msgfdbuf=letaddr,got,_=Fd.use_exn"recv_msg"fd@@funfd->do_nonblockingRead"recv_msg"(funfd->eio_recv_msgfd0buf)fdin(addr,got)letrecv_msg_with_fds~sw~max_fdsfdbuf=letaddr,got,fds=Fd.use_exn"recv_msg"fd@@funfd->do_nonblockingRead"recv_msg"(funfd->eio_recv_msgfdmax_fdsbuf)fdin(addr,got,Eio_unix.Fd.of_unix_list~swfds)externaleio_getrandom:Cstruct.buffer->int->int->int="caml_eio_posix_getrandom"letgetrandom{Cstruct.buffer;off;len}=letrecloopn=ifn=lenthen()elseloop(n+eio_getrandombuffer(off+n)(len-n))inin_worker_thread"getrandom"@@fun()->loop0letrealpathpath=in_worker_thread"realpath"@@fun()->Unix.realpathpathletread_entriesh=letrecauxacc=matchUnix.readdirhwith|"."|".."->auxacc|leaf->aux(leaf::acc)|exceptionEnd_of_file->Array.of_listaccinaux[]externaleio_readv:Unix.file_descr->Cstruct.tarray->int="caml_eio_posix_readv"externaleio_writev:Unix.file_descr->Cstruct.tarray->int="caml_eio_posix_writev"externaleio_preadv:Unix.file_descr->Cstruct.tarray->Optint.Int63.t->int="caml_eio_posix_preadv"externaleio_pwritev:Unix.file_descr->Cstruct.tarray->Optint.Int63.t->int="caml_eio_posix_pwritev"letreadvfdbufs=ifFd.is_blockingfdthenawait_readable"readv"fd;Fd.use_exn"readv"fd@@funfd->do_nonblockingRead"readv"(funfd->eio_readvfdbufs)fdletwritevfdbufs=ifFd.is_blockingfdthenawait_writable"writev"fd;Fd.use_exn"writev"fd@@funfd->do_nonblockingWrite"writev"(funfd->eio_writevfdbufs)fdletpreadv~file_offsetfdbufs=ifFd.is_blockingfdthenawait_readable"preadv"fd;Fd.use_exn"preadv"fd@@funfd->do_nonblockingRead"preadv"(funfd->eio_preadvfdbufsfile_offset)fdletpwritev~file_offsetfdbufs=ifFd.is_blockingfdthenawait_writable"pwritev"fd;Fd.use_exn"pwritev"fd@@funfd->do_nonblockingWrite"pwritev"(funfd->eio_pwritevfdbufsfile_offset)fdmoduleOpen_flags=structtypet=intletrdonly=Config.o_rdonlyletrdwr=Config.o_rdwrletwronly=Config.o_wronlyletappend=Config.o_appendletcloexec=Config.o_cloexecletcreat=Config.o_creatletdirectory=Config.o_directoryletdsync=Config.o_dsyncletexcl=Config.o_exclletnoctty=Config.o_nocttyletnofollow=Config.o_nofollowletnonblock=Config.o_nonblockletsync=Config.o_synclettrunc=Config.o_truncletresolve_beneath=Config.o_resolve_beneathletpath=Config.o_pathletempty=0let(+)=(lor)let(+?)x=function|None->x|Somey->x+yendletat_fdcwd:Unix.file_descr=Obj.magicConfig.at_fdcwdexternaleio_openat:Unix.file_descr->string->Open_flags.t->int->Unix.file_descr="caml_eio_posix_openat"leteio_openatfdpathflagsmode=letfd=Option.valuefd~default:at_fdcwdineio_openatfdpathOpen_flags.(flags+cloexec)modemoduleResolve=struct(** Resolve a path one step at a time.
This simulates how the kernel does path resolution using O_RESOLVE_BENEATH,
for kernels that don't support it.
These functions should be called from a worker sys-thread, since lookups can
be slow, especially on network file-systems and user-space mounts.
When doing lookups, we cannot ask the kernel to follow ".." links, since the
directory might get moved during the operation. e.g.
Process 1: openat [/tmp/sandbox/] "foo/../bar"
Process 2: mv /tmp/sandbox/foo /var/foo
Process 1 starts by opening "foo", then process 2 moves it, then process 1
follows the "../bar", opening /var/bar, to which it should not have access.
Instead, we keep a stack of opened directories and pop one when we see "..".
todo: possibly we should check we have search permission on ".." before
doing this.
*)typedir_stack=|BaseofUnix.file_descroption(* Base dir from user (do not close). None if cwd *)|TmpofUnix.file_descr*dir_stack(* Will be closed if in [dir_stack] at end. *)typestate={mutabledir_stack:dir_stack;(* Directories already opened, for ".." *)mutablemax_follows:int;(* Max symlinks before reporting ELOOP *)}letcurrent_dirstate=matchstate.dir_stackwith|Baseb->b|Tmp(x,_)->Somexletparse_rels=matchPath.parseswith|Relativer->r|Absolute_->raise@@Eio.Fs.err(Eio.Fs.Permission_denied(Err.Absolute_path))letdecr_max_followsstatex=ifstate.max_follows>0thenstate.max_follows<-state.max_follows-1elseraise(Unix.Unix_error(ELOOP,"resolve",x))(* Fallback for systems without O_RESOLVE_BENEATH: *)letrecresolvestate(path:Path.Rel.t)=(* traceln "Consider %a" Path.Rel.dump path; *)matchpathwith|Leaf{basename;trailing_slash}->iftrailing_slashthenbasename^"/"elsebasename|Self->"."|Parentxs->beginmatchstate.dir_stackwith|Base_->raise@@Eio.Fs.err(Permission_denied(Err.Outside_sandbox(Path.Rel.to_stringpath)))|Tmp(p,ps)->Unix.closep;state.dir_stack<-ps;resolvestatexsend|Child(x,xs)->letbase=current_dirstateinmatcheio_openatbasexOpen_flags.(nofollow+directory+?path)0with|new_base->state.dir_stack<-Tmp(new_base,state.dir_stack);resolvestatexs|exception(Unix.Unix_error((ELOOP|ENOTDIR|EMLINK|EUNKNOWNERR_),_,_)ase)->(* Note: Linux uses ELOOP or ENOTDIR. FreeBSD uses EMLINK. NetBSD uses EFTYPE. *)matchEio_unix.Private.read_link_unixbasexwith|target->decr_max_followsstatex;resolvestate(Path.Rel.concat(parse_reltarget)xs)|exceptionUnix.Unix_error_->raisee(* Not a symlink; report original error instead *)letclose_tmpstate=letrecaux=function|Base_->()|Tmp(x,xs)->Unix.closex;auxxsinauxstate.dir_stackletwith_statebasefn=(* [max_follows] matches Linux's value; see path_resolution(7) *)letstate={dir_stack=Basebase;max_follows=40}inmatchfnstatewith|x->close_tmpstate;x|exceptionex->letbt=Printexc.get_raw_backtrace()inclose_tmpstate;Printexc.raise_with_backtraceexbtlettrailing_slashx=x<>""&&x.[String.lengthx-1]='/'letopen_beneath_fallback?dirfd:base~sw~modepathflags=letpath=parse_relpathinwith_statebase@@funstate->(* Resolve the parent, then try to open the last component with [flags + nofollow].
If it's a symlink, retry with the target. *)letrecauxleaf=letbase=current_dirstateinletflags=iftrailing_slashleafthenOpen_flags.(flags+directory)elseflagsinmatcheio_openatbaseleafOpen_flags.(flags+nofollow)modewith|fd->Fd.of_unixfd~sw~blocking:false~close_unix:true|exception(Unix.Unix_error((ELOOP|ENOTDIR|EMLINK|EUNKNOWNERR_),_,_)ase)->(* Note: Linux uses ELOOP or ENOTDIR. FreeBSD uses EMLINK. NetBSD uses EFTYPE. *)matchEio_unix.Private.read_link_unixbaseleafwith|target->decr_max_followsstateleaf;aux(resolvestate(parse_reltarget))|exceptionUnix.Unix_error_->raiseeinaux(resolvestatepath)(* Resolve until the last component and call [fn dir leaf].
That returns [Error `Symlink] if [leaf] is a symlink, in
which case we read its target and continue. *)letwith_parent_loop?dirfd:basepathfn=letpath=parse_relpathinwith_statebase@@funstate->letrecauxleaf=letbase=current_dirstateinmatchfnbaseleafwith|Okx->x|Error(`Symlinke)->decr_max_followsstateleaf;matchEio_unix.Private.read_link_unixbaseleafwith|target->aux(resolvestate(parse_reltarget))|exceptionUnix.Unix_error_whenOption.is_somee->raise(Option.gete)inaux(resolvestatepath)(* If confined, resolve until the last component and call [fn dir leaf].
If unconfined, just call [fn None path].
If you need to follow [leaf] if it turns out to be a symlink,
use [with_parent_loop] instead. *)letwith_parentopfdpathfn=(* todo: use o_resolve_beneath if available *)matchfdwith|Fs->fnNonepath|Cwd->with_parent_looppath(funxy->Ok(fnxy))|Fddirfd->Fd.use_exnopdirfd@@fundirfd->with_parent_loop~dirfdpath(funxy->Ok(fnxy))letopen_unconfined~sw~modedirfdpathflags=letflags=iftrailing_slashpaththenOpen_flags.(flags+directory)elseflagsinFd.use_exn_opt"openat"dirfd@@fundirfd->eio_openatdirfdpathOpen_flags.(flags+nonblock)mode|>Fd.of_unix~sw~blocking:false~close_unix:trueletopen_beneath?dirfd~sw~modepathflags=matchOpen_flags.resolve_beneathwith|Someo_resolve_beneath->open_unconfined~sw~modedirfdpathOpen_flags.(flags+o_resolve_beneath)|None->Fd.use_exn_opt"open_beneath"dirfd@@fundirfd->open_beneath_fallback?dirfd~sw~modepathflagsendletopenat~sw~modefdpathflags=letpath=ifpath=""then"."elsepathinin_worker_thread"openat"@@fun()->matchfdwith|Fs->Resolve.open_unconfined~sw~modeNonepathflags|Cwd->Resolve.open_beneath~sw~mode?dirfd:Nonepathflags|Fddirfd->Resolve.open_beneath~sw~mode~dirfdpathflagsexternaleio_fdopendir:Unix.file_descr->Unix.dir_handle="caml_eio_posix_fdopendir"letreaddirdirfdpath=in_worker_thread"readdir"@@fun()->letuseh=matchread_entrieshwith|r->Unix.closedirh;r|exceptionex->letbt=Printexc.get_raw_backtrace()inUnix.closedirh;Printexc.raise_with_backtraceexbtinletuse_confineddirfd=Resolve.with_parent_loop?dirfdpath@@fundirfdpath->matcheio_openatdirfdpathOpen_flags.(rdonly+directory+nofollow)0with|fd->Ok(use(eio_fdopendirfd))|exception(Unix.Unix_error((ELOOP|ENOTDIR|EMLINK|EUNKNOWNERR_),_,_)ase)->Error(`Symlink(Somee))inmatchdirfdwith|Fs->use(Unix.opendirpath)|Cwd->use_confinedNone|Fddirfd->Fd.use_exn"readdir"dirfd@@fundirfd->use_confined(Somedirfd)externaleio_mkdirat:Unix.file_descr->string->Unix.file_perm->unit="caml_eio_posix_mkdirat"letmkdir~modedirfdpath=in_worker_thread"mkdir"@@fun()->Resolve.with_parent"mkdir"dirfdpath@@fundirfdpath->letdirfd=Option.valuedirfd~default:at_fdcwdineio_mkdiratdirfdpathmodeexternaleio_unlinkat:Unix.file_descr->string->bool->unit="caml_eio_posix_unlinkat"letunlink~dirdirfdpath=in_worker_thread"unlink"@@fun()->Resolve.with_parent"unlink"dirfdpath@@fundirfdpath->letdirfd=Option.valuedirfd~default:at_fdcwdineio_unlinkatdirfdpathdirexternaleio_renameat:Unix.file_descr->string->Unix.file_descr->string->unit="caml_eio_posix_renameat"letrenameold_dirold_pathnew_dirnew_path=in_worker_thread"rename"@@fun()->Resolve.with_parent"rename-old"old_dirold_path@@funold_dirold_path->Resolve.with_parent"rename-new"new_dirnew_path@@funnew_dirnew_path->letold_dir=Option.valueold_dir~default:at_fdcwdinletnew_dir=Option.valuenew_dir~default:at_fdcwdineio_renameatold_dirold_pathnew_dirnew_pathexternaleio_symlinkat:string->Unix.file_descr->string->unit="caml_eio_posix_symlinkat"letsymlink~link_tonew_dirnew_path=in_worker_thread"symlink"@@fun()->Resolve.with_parent"symlink-new"new_dirnew_path@@funnew_dirnew_path->letnew_dir=Option.valuenew_dir~default:at_fdcwdineio_symlinkatlink_tonew_dirnew_pathletread_linkdirfdpath=in_worker_thread"read_link"@@fun()->Resolve.with_parent"read_link"dirfdpath@@fundirfdpath->Eio_unix.Private.read_link_unixdirfdpathtypestatexternalcreate_stat:unit->stat="caml_eio_posix_make_stat"externaleio_fstatat:stat->Unix.file_descr->string->int->unit="caml_eio_posix_fstatat"externaleio_fstat:stat->Unix.file_descr->unit="caml_eio_posix_fstat"externalblksize:stat->(int64[@unboxed])="ocaml_eio_posix_stat_blksize_bytes""ocaml_eio_posix_stat_blksize_native"[@@noalloc]externalnlink:stat->(int64[@unboxed])="ocaml_eio_posix_stat_nlink_bytes""ocaml_eio_posix_stat_nlink_native"[@@noalloc]externaluid:stat->(int64[@unboxed])="ocaml_eio_posix_stat_uid_bytes""ocaml_eio_posix_stat_uid_native"[@@noalloc]externalgid:stat->(int64[@unboxed])="ocaml_eio_posix_stat_gid_bytes""ocaml_eio_posix_stat_gid_native"[@@noalloc]externalino:stat->(int64[@unboxed])="ocaml_eio_posix_stat_ino_bytes""ocaml_eio_posix_stat_ino_native"[@@noalloc]externalsize:stat->(int64[@unboxed])="ocaml_eio_posix_stat_size_bytes""ocaml_eio_posix_stat_size_native"[@@noalloc]externalrdev:stat->(int64[@unboxed])="ocaml_eio_posix_stat_rdev_bytes""ocaml_eio_posix_stat_rdev_native"[@@noalloc]externaldev:stat->(int64[@unboxed])="ocaml_eio_posix_stat_dev_bytes""ocaml_eio_posix_stat_dev_native"[@@noalloc]externalperm:stat->(int[@untagged])="ocaml_eio_posix_stat_perm_bytes""ocaml_eio_posix_stat_perm_native"[@@noalloc]externalmode:stat->(int[@untagged])="ocaml_eio_posix_stat_mode_bytes""ocaml_eio_posix_stat_mode_native"[@@noalloc]externalkind:stat->Eio.File.Stat.kind="ocaml_eio_posix_stat_kind"externalatime_sec:stat->(int64[@unboxed])="ocaml_eio_posix_stat_atime_sec_bytes""ocaml_eio_posix_stat_atime_sec_native"[@@noalloc]externalctime_sec:stat->(int64[@unboxed])="ocaml_eio_posix_stat_ctime_sec_bytes""ocaml_eio_posix_stat_ctime_sec_native"[@@noalloc]externalmtime_sec:stat->(int64[@unboxed])="ocaml_eio_posix_stat_mtime_sec_bytes""ocaml_eio_posix_stat_mtime_sec_native"[@@noalloc]externalatime_nsec:stat->int="ocaml_eio_posix_stat_atime_nsec"[@@noalloc]externalctime_nsec:stat->int="ocaml_eio_posix_stat_ctime_nsec"[@@noalloc]externalmtime_nsec:stat->int="ocaml_eio_posix_stat_mtime_nsec"[@@noalloc]letfstat~buffd=Fd.use_exn"fstat"fd@@funfd->eio_fstatbuffdletfstatat_confined~buf~followdirfdpath=Resolve.with_parent_loop?dirfdpath@@fundirfdpath->letdirfd=Option.valuedirfd~default:at_fdcwdineio_fstatatbufdirfdpathConfig.at_symlink_nofollow;iffollow&&kindbuf=`Symbolic_linkthenError(`SymlinkNone)elseOk()letfstatat~buf~followdirfdpath=in_worker_thread"fstat"@@fun()->matchdirfdwith|Fs->letflags=iffollowthen0elseConfig.at_symlink_nofollowineio_fstatatbufat_fdcwdpathflags|Cwd->fstatat_confined~buf~followNonepath|Fddirfd->Fd.use_exn"fstat"dirfd@@fundirfd->fstatat_confined~buf~follow(Somedirfd)pathletlseekfdoffcmd=Fd.use_exn"lseek"fd@@funfd->letcmd=matchcmdwith|`Set->Unix.SEEK_SET|`Cur->Unix.SEEK_CUR|`End->Unix.SEEK_ENDinUnix.LargeFile.lseekfd(Optint.Int63.to_int64off)cmd|>Optint.Int63.of_int64letfsyncfd=Eio_unix.run_in_systhread~label:"fsync"@@fun()->Fd.use_exn"fsync"fdUnix.fsyncletftruncatefdlen=Eio_unix.run_in_systhread~label:"ftruncate"@@fun()->Fd.use_exn"ftruncate"fd@@funfd->Unix.LargeFile.ftruncatefd(Optint.Int63.to_int64len)letpipe~sw=letunix_r,unix_w=Unix.pipe~cloexec:true()inletr=Fd.of_unix~sw~blocking:false~close_unix:trueunix_rinletw=Fd.of_unix~sw~blocking:false~close_unix:trueunix_winUnix.set_nonblockunix_r;Unix.set_nonblockunix_w;r,wmoduleProcess=structtypet={pid:int;exit_status:Unix.process_statusPromise.t;lock:Mutex.t;}(* When [lock] is unlocked, [exit_status] is resolved iff the process has been reaped. *)letexit_statust=t.exit_statusletpidt=t.pidmoduleFork_action=Eio_unix.Private.Fork_action(* Read a (typically short) error message from a child process. *)letrecread_responsefd=letbuf=Bytes.create256inmatchreadfdbuf0(Bytes.lengthbuf)with|0->""|n->Bytes.sub_stringbuf0n^read_responsefdletwith_pipefn=Switch.run@@funsw->letr,w=pipe~swinfnrwletsignaltsignal=(* We need the lock here so that one domain can't signal the process exactly as another is reaping it. *)Mutex.lockt.lock;Fun.protect~finally:(fun()->Mutex.unlockt.lock)@@fun()->ifnot(Promise.is_resolvedt.exit_status)then(Unix.killt.pidsignal)(* else process has been reaped and t.pid is invalid *)externaleio_spawn:Unix.file_descr->Eio_unix.Private.Fork_action.c_actionlist->int="caml_eio_posix_spawn"(* Wait for [pid] to exit and then resolve [exit_status] to its status. *)letreaptexit_status=Eio.Condition.loop_no_mutexEio_unix.Process.sigchld(fun()->Mutex.lockt.lock;matchUnix.waitpid[WNOHANG]t.pidwith|0,_->Mutex.unlockt.lock;None(* Not ready; wait for next SIGCHLD *)|p,status->assert(p=t.pid);Promise.resolveexit_statusstatus;Mutex.unlockt.lock;Some())letspawn~swactions=with_pipe@@funerrors_rerrors_w->Eio_unix.Private.Fork_action.with_actionsactions@@func_actions->Switch.checksw;letexit_status,set_exit_status=Promise.create()inlett=letpid=Fd.use_exn"errors-w"errors_w@@funerrors_w->Eio.Private.Trace.with_span"spawn"@@fun()->eio_spawnerrors_wc_actionsinFd.closeerrors_w;{pid;exit_status;lock=Mutex.create()}inlethook=Switch.on_release_cancellablesw(fun()->(* Kill process (if still running) *)signaltSys.sigkill;(* The switch is being released, so either the daemon fiber got
cancelled or it hasn't started yet (and never will start). *)ifnot(Promise.is_resolvedt.exit_status)then((* Do a (non-cancellable) waitpid here to reap the child. *)reaptset_exit_status))inFiber.fork_daemon~sw(fun()->reaptset_exit_status;Switch.remove_hookhook;`Stop_daemon);(* Check for errors starting the process. *)matchread_responseerrors_rwith|""->t(* Success! Execing the child closed [errors_w] and we got EOF. *)|err->failwitherrend