123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179(*
* Copyright (c) 2022-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.
*)openImportincludeControl_file_intfmodulePlv3=structincludePayload_v3letof_bin_string=Irmin.Type.of_bin_stringt|>Irmin.Type.unstageendmodulePlv4=structincludePayload_v4letof_bin_string=Irmin.Type.of_bin_stringt|>Irmin.Type.unstageletto_bin_string=Irmin.Type.to_bin_stringt|>Irmin.Type.unstageendmoduleVersion=Irmin_pack.VersionmoduleData(Io:Io.S)=struct(** Type of what's encoded in the control file. The variant tag is encoded as
a [Version.t]. *)typet=V3ofPlv3.t|V4ofPlv4.tletto_bin_string=function|V3_->assertfalse|V4payload->Version.to_bin`V4^Plv4.to_bin_stringpayloadletof_bin_strings=letopenResult_syntaxinletlen=String.lengthsinlet*left,right=tryOk(String.subs08,String.subs8(len-8))withInvalid_argument_->Error`Corrupted_control_fileinlet*version=matchVersion.of_binleftwith|None->Error(`Unknown_major_pack_versionleft)|Some`V3whenlen>Io.page_size->Error`Corrupted_control_file|Some`V3->Ok`V3|Some`V4->Ok`V4|Some(`V1|`V2)->assertfalseinmatchversionwith|`V3->(matchPlv3.of_bin_stringrightwith|Okx->Ok(V3x)|Error_->Error`Corrupted_control_file)|`V4->(matchPlv4.of_bin_stringrightwith|Okx->Ok(V4x)|Error_->Error`Corrupted_control_file)endmoduleMake(Io:Io.S)=structmoduleIo=IomoduleData=Data(Io)typet={io:Io.t;mutablepayload:Latest_payload.t}letpre_hash_payload=Irmin.Type.(unstage(pre_hashLatest_payload.t))letchecksumpayload=letopenCheckseuminletresult=refAdler32.defaultinpre_hash_payload{payloadwithchecksum=Int63.zero}(funstr->result:=Adler32.digest_stringstr0(String.lengthstr)!result);Int63.of_int(Optint.to_int!result)letchecksum_is_validpayload=Int63.equal(checksumpayload)payload.checksumletupgrade_v3_to_v4(pl:Payload_v3.t):Payload_v4.t=letchunk_start_idx=ref0inletstatus=matchpl.statuswith|From_v1_v2_post_upgradex->Payload_v4.From_v1_v2_post_upgradex|From_v3_no_gc_yet->No_gc_yet|From_v3_used_non_minimal_indexing_strategy->Used_non_minimal_indexing_strategy|From_v3_gcedx->chunk_start_idx:=x.generation;Gced{suffix_start_offset=x.suffix_start_offset;generation=x.generation;latest_gc_target_offset=x.suffix_start_offset;suffix_dead_bytes=Int63.zero;}|T1|T2|T3|T4|T5|T6|T7|T8|T9|T10|T11|T12|T13|T14|T15->(* Unreachable *)assertfalsein{dict_end_poff=pl.dict_end_poff;(* When upgrading from v3 to v4, there is only one (appendable) chunk,
which is the existing suffix, so we set the new [appendable_chunk_poff]
to [pl.suffix_end_poff]. *)appendable_chunk_poff=pl.suffix_end_poff;status;upgraded_from_v3_to_v4=true;checksum=Int63.zero;chunk_start_idx=!chunk_start_idx;chunk_num=1;}letwriteiopayload=letpayload={payloadwithPayload_v4.checksum=checksumpayload}inlets=Data.(to_bin_string(V4payload))in(* The data must fit inside a single page for atomic updates of the file.
This is only true for some file systems. This system will have to be
reworked for [V4]. *)assert(String.lengths<=Io.page_size);Io.write_stringio~off:Int63.zerosletreadio=letopenResult_syntaxinlet*string=Io.read_all_to_stringioin(* Since the control file is expected to fit in a page,
[read_all_to_string] should be atomic for most filesystems.
If [string] is larger than a page, it either means that the file can be
corrupted or that the major version is not supported. Either way it will
be detected by [Data.of_bin_string] or have an invalid checksum. *)let*payload=Data.of_bin_stringstringinmatchpayloadwith|V3payload->Ok(upgrade_v3_to_v4payload)|V4payload->ifchecksum_is_validpayloadthenOkpayloadelseError`Corrupted_control_fileletcreate_rw~path~overwritepayload=letopenResult_syntaxinlet*io=Io.create~path~overwriteinlet+()=writeiopayloadin{io;payload}letopen_~path~readonly=letopenResult_syntaxinlet*io=Io.open_~path~readonlyinlet+payload=readioin{io;payload}letcloset=Io.closet.ioletreadonlyt=Io.readonlyt.ioletpayloadt=t.payloadletreloadt=letopenResult_syntax inifnot@@Io.readonlyt.iothenError`Rw_not_allowedelselet+payload=readt.ioint.payload<-payloadletset_payloadtpayload=letopenResult_syntaxinlet+()=writet.iopayloadint.payload<-payloadletfsynct=Io.fsynct.ioend