123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227(*
* Copyright (c) 2009-2013 Anil Madhavapeddy <anil@recoil.org>
* Copyright (c) 2013 Thomas Gazagnaire <thomas@gazagnaire.org>
*
* 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.
*)moduleSM=Map.Make(String)typefile_info={chunk_digests:stringlist;file_digest:string;size:int;}typet=stringSM.t*file_infoSM.tletmake()=(SM.empty,SM.empty)moduleFilename=structincludeFilename(* Always use Unix-style filenames for keys *)letdir_sep="/"letis_dir_sepsi=s.[i]='/'letconcatdirnamefilename=letl=String.lengthdirnameinifl=0||is_dir_sepdirname(l-1)thendirname^filenameelsedirname^dir_sep^filenameend(* Walk directory and call walkfn on every file that matches extension ext *)letwalk_directory_treetextswalkfnroot_dir=(* Recursive directory walker *)letrecwalk_dirdirt=letdh=Unix.opendirdirinletrecrepeatt=matchUnix.readdirdhwith|exceptionEnd_of_file->t|"."|".."->repeatt|f->(letn=Filename.concatdirfinifSys.is_directorynthenrepeat(walk_dirnt)elseletname=String.subn2(String.lengthn-2)in(* If extension list is empty then let all through, otherwise white list *)match(exts,Filename.extensionf)with|[],_->repeat(walkfntroot_dirname)|exts,ewhene<>""&&List.mem(String.sube1(String.lengthe-1))exts->repeat(walkfntroot_dirname)|_->repeatt)inletresult=repeattinUnix.closedirdh;resultinUnix.chdirroot_dir;walk_dir"."tletnow()=tryfloat_of_string(Sys.getenv"SOURCE_DATE_EPOCH")withNot_found->Unix.gettimeofday()letoutput_generated_byocbinary=lett=now()inletmonths=[|"Jan";"Feb";"Mar";"Apr";"May";"Jun";"Jul";"Aug";"Sep";"Oct";"Nov";"Dec";|]inletdays=[|"Sun";"Mon";"Tue";"Wed";"Thu";"Fri";"Sat"|]inlettime=Unix.gmtimetinletdate=Printf.sprintf"%s, %d %s %d %02d:%02d:%02d GMT"days.(time.Unix.tm_wday)time.Unix.tm_mdaymonths.(time.Unix.tm_mon)(time.Unix.tm_year+1900)time.Unix.tm_hourtime.Unix.tm_mintime.Unix.tm_secinPrintf.fprintfoc"(* Generated by: %s\n Creation date: %s *)\n\n"binarydate(** Generate a set of MD5 hashed blocks, abort on collision *)letscan_file(chunk_info,file_info)rootname=letfull_name=Filename.concatrootnameinletstats=Unix.statfull_nameinletsize=stats.Unix.st_sizeinletfin=open_in_binfull_nameinletbuf=Buffer.createsizeinBuffer.add_channelbuffinsize;lets=Buffer.contentsbufinclose_infin;letrev_chunks=ref[]inletcalc_chunkchunk_infob=letdigest=Digest.to_hex(Digest.stringb)inrev_chunks:=digest::!rev_chunks;matchSM.find_optdigestchunk_infowith|None->SM.adddigestbchunk_info|Somecur->ifnot(String.equalcurb)thenfailwith("MD5 hash collision in file "^name)elsechunk_infoin(* Split the file as a series of chunks, of size up to 4096 (to simulate reading sectors) *)letsec=4096in(* sector size *)letrecconsumeidxchunk_info=ifidx=sizethenchunk_info(* EOF *)elseifidx+sec<sizethenletchunk_info'=calc_chunkchunk_info(String.subsidxsec)inconsume(idx+sec)chunk_info'else(* final chunk, short *)calc_chunkchunk_info(String.subsidx(size-idx))in(* consume fills !rev_chunks as a side effect, so sequentialise this*)letci=consume0chunk_infoinletentry={chunk_digests=List.rev!rev_chunks;file_digest=Digest.(to_hex(strings));size=String.lengths;}in(ci,SM.addnameentryfile_info)letoutput_implementation(chunk_info,file_info)oc=letpffmt=Printf.fprintfocfmtinpf"module Internal = struct\n";SM.iter(funnamechunk->pf" let d_%s = %S\n\n"namechunk)chunk_info;pf" let file_chunks = function\n";SM.iter(funname{chunk_digests;_}->pf" | %S | \"/%s\" -> Some ["name(String.escapedname);List.iter(pf" d_%s;")chunk_digests;pf" ]\n")file_info;pf" | _ -> None\n\n";pf" let file_list = [ ";SM.iter(funname_->pf"%S; "name)file_info;pf"]\n";pf"end\n"letoutput_plain_skeleton_ml(_,file_info)oc=letpffmt=Printf.fprintfocfmtinpf{|
let file_list = Internal.file_list
let read name =
match Internal.file_chunks name with
| None -> None
| Some c -> Some (String.concat "" c)
let hash = function
|};SM.iter(funname{file_digest;_}->pf" | %S | \"/%s\" -> Some \"%s\"\n"name(String.escapedname)file_digest)file_info;pf" | _ -> None\n\n";pf"let size = function\n";SM.iter(funname{size;_}->pf" | %S | \"/%s\" -> Some %d\n"name(String.escapedname)size)file_info;pf" | _ -> None\n"letoutput_lwt_skeleton_mloc=letdays,ps=Ptime.Span.to_d_ps@@Ptime.to_span(matchPtime.of_float_s(now())with|None->assertfalse|Somex->x)inPrintf.fprintfoc{|
open Lwt
include Mirage_kv_mem
let file_content name =
match Internal.file_chunks name with
| None -> Lwt.fail_with ("expected file content, found no blocks " ^ name)
| Some blocks -> Lwt.return (String.concat "" blocks)
let add store name =
file_content name >>= fun data ->
set store (Mirage_kv.Key.v name) data >>= function
| Ok () -> Lwt.return_unit
| Error e -> Lwt.fail_with (Fmt.to_to_string pp_write_error e)
let connect () =
connect ~now:(fun () -> Ptime.v (%d, %LdL)) () >>= fun store ->
Lwt_list.iter_s (add store) Internal.file_list >|= fun () -> store
|}dayspsletoutput_lwt_skeleton_mlioc=Printf.fprintfoc{|include Mirage_kv.RO
val connect : unit -> t Lwt.t
|}