123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293(*
* 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.
*)open!ImportopenIo_intfmoduleSyscalls=Index_unix.Syscalls(* File utils, taken from index.unix package.
These functions need to read from a loop because the underlying
implementation will not read/write more than a constant called
[UNIX_BUFFER_SIZE]. *)moduleUtil=structletreally_writefdfd_offsetbufferbuffer_offsetlength=letrecauxfd_offsetbuffer_offsetlength=letw=Syscalls.pwrite~fd~fd_offset~buffer~buffer_offset~lengthinifw=0||w=lengththen()else(aux[@tailcall])Int63.Syntax.(fd_offset+Int63.of_intw)(buffer_offset+w)(length-w)inauxfd_offsetbuffer_offsetlengthletreally_readfdfd_offsetlengthbuffer=letrecauxfd_offsetbuffer_offsetlength=letr=Syscalls.pread~fd~fd_offset~buffer~buffer_offset~lengthinifr=0thenbuffer_offset(* end of file *)elseifr=lengththenbuffer_offset+relse(aux[@tailcall])Int63.Syntax.(fd_offset+Int63.of_intr)(buffer_offset+r)(length-r)inauxfd_offset0lengthendmoduletypeS=SmoduleUnix=structtypemisc_error=Unix.error*string*stringletunix_error_t=Irmin.Type.(mapstring(fun_str->assertfalse)Unix.error_message)letmisc_error_t=Irmin.Type.(tripleunix_error_tstringstring)typecreate_error=[`Io_miscofmisc_error|`File_existsofstring]typeopen_error=[`Io_miscofmisc_error|`No_such_file_or_directory|`Not_a_file]typeread_error=[`Io_miscofmisc_error|`Read_out_of_bounds|`Closed|`Invalid_argument]typewrite_error=[`Io_miscofmisc_error|`Ro_not_allowed|`Closed]typeclose_error=[`Io_miscofmisc_error|`Double_close]typemkdir_error=[`Io_miscofmisc_error|`File_existsofstring|`No_such_file_or_directory|`Invalid_parent_directory]letraise_misc_error(x,y,z)=raise(Unix.Unix_error(x,y,z))letcatch_misc_errorf=tryOk(f())withUnix.Unix_error(e,s1,s2)->Error(`Io_misc(e,s1,s2))typet={fd:Unix.file_descr;mutableclosed:bool;readonly:bool;path:string;}letclassify_pathp=Unix.(trymatch(statp).st_kindwith|S_REG->`File|S_DIR->`Directory|_->`Otherwith_->`No_such_file_or_directory)letdefault_create_perm=0o644letdefault_open_perm=0o644letdefault_mkdir_perm=0o755letcreate~path~overwrite=trymatchSys.file_existspathwith|false->letfd=Unix.(openfilepath[O_CREAT;O_RDWR;O_EXCL;O_CLOEXEC]default_create_perm)inOk{fd;closed=false;readonly=false;path}|true->(matchoverwritewith|true->(* The file exists, truncate it and use it. An exception will be
triggered if we don't have the permissions *)letfd=Unix.(openfilepath[O_RDWR;O_CLOEXEC;O_TRUNC]default_create_perm)inOk{fd;closed=false;readonly=false;path}|false->Error(`File_existspath))with|Unix.Unix_error(e,s1,s2)->Error(`Io_misc(e,s1,s2))|Sys_error_->assertfalseletopen_~path~readonly=matchclassify_pathpathwith|`Directory|`Other->Error`Not_a_file|`No_such_file_or_directory->Error`No_such_file_or_directory|`File->(letmode=Unix.(ifreadonlythenO_RDONLYelseO_RDWR)intryletfd=Unix.(openfilepath[mode;O_CLOEXEC]default_open_perm)inOk{fd;closed=false;readonly;path}withUnix.Unix_error(e,s1,s2)->Error(`Io_misc(e,s1,s2)))letcloset=matcht.closedwith|true->Error`Double_close|false->(t.closed<-true;(* mark [t] as closed, even if [Unix.close] fails, since it is recommended
to not retry after an error. see: https://man7.org/linux/man-pages/man2/close.2.html *)tryUnix.closet.fd;Ok()withUnix.Unix_error(e,s1,s2)->Error(`Io_misc(e,s1,s2)))letwrite_exnt~off~lens=ifString.lengths<lenthenraise(Errors.Pack_error`Invalid_argument);match(t.closed,t.readonly)with|true,_->raiseErrors.Closed|_,true->raiseErrors.RO_not_allowed|_->(* Bytes.unsafe_of_string usage: s has shared ownership; we assume that
Util.really_write does not mutate buf (i.e., only needs shared ownership). This
usage is safe. *)letbuf=Bytes.unsafe_of_stringsinlet()=Util.really_writet.fdoffbuf0leninIndex.Stats.add_writelen;()letwrite_stringt~offs=letlen=String.lengthsintryOk(write_exnt~off~lens)with|Errors.Closed->Error`Closed|Errors.RO_not_allowed->Error`Ro_not_allowed|Unix.Unix_error(e,s1,s2)->Error(`Io_misc(e,s1,s2))letfsynct=match(t.closed,t.readonly)with|true,_->Error`Closed|_,true->Error`Ro_not_allowed|_->(tryUnix.fsynct.fd;Ok()withUnix.Unix_error(e,s1,s2)->Error(`Io_misc(e,s1,s2)))letread_exnt~off~lenbuf=iflen>Bytes.lengthbufthenraise(Errors.Pack_error`Invalid_argument);matcht.closedwith|true->raiseErrors.Closed|false->letnread=Util.really_readt.fdofflenbufinIndex.Stats.add_readnread;ifnread<>lenthen(* didn't manage to read the desired amount; in this case the interface seems to
require we return `Read_out_of_bounds FIXME check this, because it is unusual
- the normal API allows return of a short string *)raise(Errors.Pack_error`Read_out_of_bounds)letread_to_stringt~off~len=letbuf=Bytes.createlenintryread_exnt~off~lenbuf;(* Bytes.unsafe_to_string usage: buf is local to this function, so uniquely
owned. We assume read_exn returns unique ownership of buf to this function. Then
at the call to Bytes.unsafe_to_string we give up unique ownership of buf for
ownership of the string. This is safe. *)Ok(Bytes.unsafe_to_stringbuf)with|Errors.Pack_error((`Invalid_argument|`Read_out_of_bounds)ase)->Errore|Errors.Closed->Error`Closed|Unix.Unix_error(e,s1,s2)->Error(`Io_misc(e,s1,s2))letpage_size=4096letread_all_to_stringt=letopenResult_syntaxinlet*()=ift.closedthenError`ClosedelseOk()inletbuf=Buffer.create0inletlen=page_sizeinletbytes=Bytes.createleninletrecaux~off=letnread=Syscalls.pread~fd:t.fd~fd_offset:off~buffer:bytes~buffer_offset:0~length:leninifnread>0then(Index.Stats.add_readnread;Buffer.add_subbytesbufbytes0nread;ifnread=lenthenaux~off:Int63.(addoff(of_intnread)))intryaux~off:Int63.zero;Ok(Buffer.contentsbuf)withUnix.Unix_error(e,s1,s2)->Error(`Io_misc(e,s1,s2))letread_sizet=matcht.closedwith|true->Error`Closed|false->(tryOkUnix.LargeFile.((fstatt.fd).st_size|>Int63.of_int64)withUnix.Unix_error(e,s1,s2)->Error(`Io_misc(e,s1,s2)))letsize_of_paths=letopenResult_syntaxinlet*io=open_~path:s~readonly:trueinletres=matchread_sizeiowith|Error`Closed->assertfalse|Error(`Io_misc_)asx->x|Ok_asx->xinmatchcloseiowith|Error`Double_close->assertfalse|Error(`Io_misc_)asx->x|Ok()->resletreadonlyt=t.readonlyletpatht=t.pathletmove_file~src~dst=trySys.renamesrcdst;Ok()withSys_errormsg->Error(`Sys_errormsg)letcopy_file~src~dst=letcmd=Filename.quote_command"cp"["-p";src;dst]inmatchSys.commandcmdwith|0->Ok()|n->Error(`Sys_error(Int.to_stringn))letmkdirpath=match(classify_path(Filename.dirnamepath),classify_pathpath)with|`Directory,`No_such_file_or_directory->(tryUnix.mkdirpathdefault_mkdir_perm;Ok()withUnix.Unix_error(e,s1,s2)->Error(`Io_misc(e,s1,s2)))|`Directory,(`File|`Directory|`Other)->Error(`File_existspath)|`No_such_file_or_directory,`No_such_file_or_directory->Error`No_such_file_or_directory|_->Error`Invalid_parent_directoryletunlinkpath=trySys.removepath;Ok()withSys_errormsg->Error(`Sys_errormsg)end