123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273(*
* Copyright (c) 2018-2022 Tarides <contact@tarides.com>
*
* 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.
*)(** The mapping file is a sorted list of intervals which allows the mapping from
sparse virtual offsets to compact physical offsets. It is used by the GC to
compact the live reachable data into the [prefix] file and delete the dead
intervals of data from the disk.
The concrete representation is a sorted array of triplets
[(virtual_offset, physical_offset, length)]. Given a virtual offset, its
physical location can be found by doing a binary search. *)open!ImportincludeMapping_file_intfmoduleBigArr1=Bigarray.Array1typeint64_bigarray=(int64,Bigarray.int64_elt,Bigarray.c_layout)Bigarray.Array1.t(* Set to 0 until we find decide what to do about sequential traversal of pack files *)letgap_tolerance=0moduleInt64_mmap:sigtypet=private{fn:string;fd:Unix.file_descr;mutablearr:int64_bigarray;}valopen_ro:fn:string->sz:int->t(** NOTE [open_ ~fn ~sz] can use [sz=-1] to open with size based on the size
of the underlying file *)valopen_rw:string->tvalclose:t->unitend=structtypet={fn:string;fd:Unix.file_descr;mutablearr:int64_bigarray}(* NOTE sz=-1 is recognized by [map_file] as "derive from size of file"; if we want a
different size (eg because we want the file to grow) we can provide it explicitly *)letopen_ro~fn~sz=letshared=falseinassert(Sys.file_existsfn);letfd=Unix.(openfilefn[O_RDONLY]0o660)inletarr=letopenBigarrayinUnix.map_filefdInt64c_layoutshared[|sz|]|>array1_of_genarrayin{fn;fd;arr}letopen_rwfn=(* NOTE following mmap is shared *)letshared=trueinassert(Sys.file_existsfn);letfd=Unix.(openfilefn[O_RDWR]0o660)inletarr=letopenBigarrayinUnix.map_filefdInt64c_layoutshared[|-1|]|>array1_of_genarrayin{fn;fd;arr}letcloset=Unix.closet.fd;(* following tries to make the array unreachable, so GC'able; however, no guarantee
that arr actually is unreachable *)t.arr<-Bigarray.(Array1.createInt64c_layout0);()end(** The mapping file is created from a decreasing list of
[(virtual_offset, 0, length)]. We first need to reverse it such that virtual
offsets are in increasing order. *)letrev_inplace(src:int64_bigarray):unit=letsrc_sz=BigArr1.dimsrcinlet_=assert(src_sz>=3);assert(src_szmod3=0)inletrecrevij=ifi<jthen(letioff,ilen=(src.{i},src.{i+2})inletjoff,jlen=(src.{j},src.{j+2})insrc.{i}<-joff;src.{i+2}<-jlen;src.{j}<-ioff;src.{j+2}<-ilen;rev(i+3)(j-3))inrev0(src_sz-3)letint64_endian:int64->int64=funi->ifSys.big_endianthen((* We are currently on a BE platform but the ints are encoded as LE in the
file. We've just read a LE int using a BE decoding scheme. Let's fix
this.
The first step is to set [buf] to contain exactly what is stored on
disk. Since the current platform is BE, we've interpreted what was
written on disk using a BE decoding scheme. To do the opposite operation
we must use a BE encoding scheme, hence [set_int64_be].
Now that [buf] mimics what was on disk, the second step consist of
decoding it using a LE function, hence [get_int64_le]. *)letbuf=Bytes.create8inBytes.set_int64_bebuf0i;Bytes.get_int64_lebuf0)elseiletconv_int64i=Int64.to_int(int64_endiani)(** We then replace the [0] component of the triplets with the accumulated
length. This yields triplets [(virtual_offset, physical_offset, length)],
which will allow us to map virtual offsets to their physical location in the
prefix file. *)letset_prefix_offsetssrc=letsrc_sz=BigArr1.dimsrcinletrecgoipoff=ifi<src_szthen(src.{i+1}<-int64_endianpoff;letlen=int64_endiansrc.{i+2}ingo(i+3)(Int64.addpofflen))ingo0Int64.zeromoduleMake(Io:Io.S)=structmoduleIo=IomoduleErrs=Io_errors.Make(Io)moduleAo=Append_only_file.Make(Io)(Errs)typet={arr:int64_bigarray;root:string;generation:int}letopen_map~root~generation=letpath=Irmin_pack.Layout.V4.mapping~generation~rootinmatchIo.classify_pathpathwith|`File->(letmmap=Int64_mmap.open_ro~fn:path~sz:(-1)inletarr=mmap.arrinletlen=BigArr1.dimarrinmatchlen>0&&lenmod3=0with|true->Int64_mmap.closemmap;Ok{root;generation;arr}|false->Error(`Corrupted_mapping_file(__FILE__^": mapping mmap size did not meet size requirements")))|_->Error`No_such_file_or_directoryletcreate?report_mapping_size~root~generation~register_entries()=assert(generation>0);letopenResult_syntaxinletpath=Irmin_pack.Layout.V3.mapping~generation~rootinlet*()=ifSys.word_size<>64thenError`Gc_forbidden_on_32bit_platformselseOk()in(* Unlink residual and ignore errors (typically no such file) *)Io.unlinkpath|>ignore;(* Create [file] *)let*file=Ao.create_rw~path~overwrite:true~auto_flush_threshold:1_000_000~auto_flush_procedure:`Internalin(* Fill and close [file] *)letappend_entry~off~len=(* Write [off, 0, len] in little-endian encoding for portability.
The [0] reserves the space for the future prefix offset. *)letbuffer=Bytes.create24inBytes.set_int64_lebuffer0(Int63.to_int64off);Bytes.set_int64_lebuffer8Int64.zero;Bytes.set_int64_lebuffer16(Int64.of_intlen);(* Bytes.unsafe_to_string usage: buffer is uniquely owned; we assume
Bytes.set_int64_le returns unique ownership; we give up ownership of buffer in
conversion to string. This is safe. *)Ao.append_exnfile(Bytes.unsafe_to_stringbuffer)in(* Check if we can collapse consecutive entries *)letcurrent_entry=refNoneinletregister_entry~off~len=letcurrent=match!current_entrywith|None->(off,len)|Some(off',len')->ifoff>=off'theninvalid_arg"register_entry: offsets are not strictly decreasing";letdist=Int63.to_int(Int63.suboff'off)inifdist<=len+gap_tolerancethen(off,dist+len')else(append_entry~off:off'~len:len';(off,len))incurrent_entry:=Somecurrentinlet*()=Errs.catch(fun()->register_entries~register_entry;(* Flush pending entry *)match!current_entrywith|None->()|Some(off,len)->append_entry~off~len)inlet*()=Ao.flushfileinlet*()=Ao.closefilein(* Reopen [file] but as an mmap *)letfile=Int64_mmap.open_rwpathinlet*()=Errs.catch(fun()->rev_inplacefile.arr;set_prefix_offsetsfile.arr)in(* Flush and close new mapping [file] *)let*()=Errs.catch(fun()->Unix.fsyncfile.fd)inInt64_mmap.closefile;let*mapping_size=Io.size_of_pathpathinOption.iter(funf->fmapping_size)report_mapping_size;(* Open created map *)open_map~root~generationletentry_countarr=BigArr1.dimarr/3letentry_idxi=i*3letentry_offarri=arr.{entry_idxi}|>conv_int64|>Int63.of_intletentry_poffarri=arr.{entry_idxi+1}|>conv_int64|>Int63.of_intletentry_lenarri=arr.{entry_idxi+2}|>conv_int64letiter_exn{arr;_}f=fori=0toentry_countarr-1dof~off:(entry_offarri)~len:(entry_lenarri)doneletitertf=Errs.catch(fun()->iter_exntf;())typeentry={off:int63;poff:int63;len:int}letfind_nearest_leq{arr;_}off=letgetarri=arr.{entry_idxi}|>conv_int64inmatchUtils.nearest_leq~arr~get~lo:0~hi:(entry_countarr-1)~key:(Int63.to_intoff)with|`All_gt_key->None|`Somei->letoff=entry_offarriinletpoff=entry_poffarriinletlen=entry_lenarriinSome{off;poff;len}end