12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697(*
* Copyright (C) 2023 Thomas Leonard
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*)moduleM=Map.Make(Int)moduleCount=structletcreate()=refM.emptyletgettfd=M.find_optfd!t|>Option.value~default:0letincrtfd=letincx=Some(1+Option.valuex~default:0)int:=M.updatefdinc!tletdecrtfd=matchgettfdwith|iwheni<=0->assertfalse|1->t:=M.removefd!t;`Unused|i->t:=M.addfd(predi)!t;`Still_neededendtypeaction={src:int;dst:int}letplanmapping=letmapping=List.fold_left(funacc(dst,src)->ifM.memdstaccthenFmt.invalid_arg"FD %d assigned twice!"dst;M.adddstsrcacc)M.emptymappinginletplan=ref[]inletdup2srcdst=plan:={src;dst}::!planinletusers_of=Count.create()in(* First, for any FDs that map to themselves we emit (fd, fd) and then forget about it,
as this doesn't interfere with anything else.
We also set [users_of] to track how many times each FD is needed. *)letmapping=mapping|>M.filter(fundstsrc->ifsrc=dstthen(dup2srcsrc;false)(* Just clear the close-on-exec flag. *)else(Count.incrusers_ofsrc;true))inlettmp=ref(-1)in(* The FD we dup'd to the temporary FD when breaking cycles. *)letrecno_usersdst=(* Nothing requires the old value of [dst] now,
so if we wanted to put something there, do it. *)M.find_optdstmapping|>Option.iter(funsrc->dupsrcdst)anddupsrcdst=(* Duplicate [src] as [dst]. *)ifsrc=!tmpthen((* We moved [src] to [tmp] to break a cycle, so use [tmp] instead.
Also, there's nothing to do after this as the cycle is broken. *)dup2(-1)dst;)else(dup2srcdst;(* Record that [dst] no longer depends on [src]. *)matchCount.decrusers_ofsrcwith|`Still_needed->()|`Unused->no_userssrc)in(* Find any loose ends and work backwards.
Note: we need to do this in two steps because [dup] modifies [users_of]. *)mapping|>M.filter(fundst_src->Count.getusers_ofdst=0)(* FDs with no dependants *)|>M.iter(fundstsrc->dupsrcdst);(* At this point there are no loose ends; we have nothing but cycles left. *)(* M.iter (fun _ v -> assert (v = 1)) !users_of; *)(* For each cycle, break it at one point using the temporary FD.
It's safe to allocate the temporary FD now because every FD we plan to use is already allocated. *)letrecbreak_cycles()=matchM.min_binding_opt!users_ofwith(* Pick any remaining FD. *)|None->()|Some(src,_)->dup2src(-1);(* Duplicate [src] somewhere. *)tmp:=src;(* Remember that when we try to use it later. *)(* The FD that needed [src] can now use [tmp] instead: *)letstate=Count.decrusers_ofsrcinassert(state=`Unused);no_userssrc;(* Free this cycle. *)break_cycles()(* Free any other cycles. *)inbreak_cycles();List.rev!plan