123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464letclose_in=close_inletclose_out=close_outletclose_both(ic,oc)=matchclose_outocwith|()->close_inic|exceptionexn->close_inic;Exn.reraiseexn;;letinput_lines=letrecloopicacc=matchinput_lineicwith|exceptionEnd_of_file->List.revacc|line->loopic(line::acc)infunic->loopic[];;letinput_zero_from_bufferfrombuf=matchString.index_from_optbuffrom'\x00'with|None->None|Someeos->Some(String.subbuf~pos:from~len:(eos-from),eos+1);;(* Note, the complexity of this function will be bad if the zero-separated
elements are much larger than the current input buffer *)letinput_zero_separated=(* Take all the \0-terminated strings from [buf], return the scanned list and
the remainder *)letrecscan_inputs_buffrombufacc=(* note that from is untouched if input_zero_from_buffer returns None *)matchinput_zero_from_bufferfrombufwith|Some(istr,from)->scan_inputs_buffrombuf(istr::acc)|None->lettotal_len=String.lengthbufiniftotal_len>fromthen(letrest=String.subbuf~pos:from~len:(total_len-from)inSomerest,acc)elseNone,accinletibuf_size=65536inletibuf=Bytes.createibuf_sizeinletrecinput_loopicremacc=letres=inputicibuf0ibuf_sizeinifres=0then((* end of file, check if there is a remainder, and return the results *)matchremwith|Somerem->List.rev(rem::acc)|None->List.revacc)else((* new input, append remainder and scan it *)letactual_input=Bytes.sub_stringibuf~pos:0~len:resinletactual_input=matchremwith|None->actual_input|Somerem->rem^actual_inputinletrem,acc=scan_inputs_buf0actual_inputaccininput_loopicremacc)infunic->input_loopicNone[];;letcopy_channels=letbuf_len=65536inletglobal_buf=Bytes.createbuf_leninletrecloopbuficoc=matchinputicbuf0buf_lenwith|0->()|n->outputocbuf0n;loopbuficocinletbusy=reffalseinfunicoc->if!busythenloop(Bytes.createbuf_len)icocelse(busy:=true;matchloopglobal_buficocwith|()->busy:=false|exceptionexn->busy:=false;Exn.reraiseexn);;letsetup_copy?(chmod=Fun.id)~src~dst()=letic=Stdlib.open_in_binsrcinletoc=tryletperm=(Unix.fstat(Unix.descr_of_in_channelic)).st_perm|>chmodinStdlib.open_out_gen[Open_wronly;Open_creat;Open_trunc;Open_binary]permdstwith|exn->close_inic;Exn.reraiseexninic,oc;;moduleCopyfile=struct(* Bindings to mac's fast copy function. It's similar to a hardlink, except
it does COW when edited. It will also default back to regular copying if
it fails for w/e reason *)externalcopyfile:string->string->unit="stdune_copyfile"externalsendfile:src:Unix.file_descr->dst:Unix.file_descr->int->unit="stdune_sendfile"letavailable=matchPlatform.OS.valuewith|Darwin->`Copyfile|Linux->`Sendfile|_->`Nothing;;letsendfile_with_fallback=letsetup_copy?(chmod=Fun.id)~src~dst()=matchUnix.openfilesrc[O_RDONLY;O_CLOEXEC]0with|exceptionUnix.Unix_error(Unix.ENOENT,_,_)->Error`Src_missing|fd_src->(matchUnix.fstatfd_srcwith|exceptionexn->Unix.closefd_src;Error(`Exn(Exn_with_backtrace.captureexn))|src_stat->(matchsrc_stat.st_kindwith|S_DIR->Error`Src_is_a_dir|_->letopenResult.Oinlet+fd_dst,src_size=matchletdst_perm=chmodsrc_stat.st_perminUnix.openfiledst[O_WRONLY;O_CREAT;O_TRUNC;O_CLOEXEC]dst_permwith|fd_dst->Ok(fd_dst,src_stat.st_size)|exceptionexn->Unix.closefd_src;(matchexnwith|Unix.Unix_error(Unix.EISDIR,_,_)->Error`Dst_is_a_dir|_->Error(`Exn(Exn_with_backtrace.captureexn)))infd_src,fd_dst,src_size))infun?chmod~src~dst()->(* All of this exception translation is done for now to maintain the
same error messages as the other file copying functions.
Eventually, we should stop using exceptions for signalling these
errors. But that's a bit of a large change since there's a lot of
exception catching to audit. *)matchsetup_copy?chmod~src~dst()with|Error(`Exnexn)->Exn_with_backtrace.reraiseexn|Error`Src_is_a_dir->raise(Sys_error"Is a directory")|Error`Dst_is_a_dir->letmessage=Printf.sprintf"%s: Is a directory"dstinraise(Sys_errormessage)|Error`Src_missing->letmessage=Printf.sprintf"%s: No such file or directory"srcinraise(Sys_errormessage)|Ok(src,dst,src_size)->letclose_fds()=Unix.closesrc;Unix.closedstin(matchsendfile~src~dstsrc_sizewith|exceptionUnix.Unix_error(EINVAL,"sendfile",_)->Exn.protectx(Unix.in_channel_of_descrsrc,Unix.out_channel_of_descrdst)(* we make sure to close the fd's with the channel api to make
sure everything has been flushed *)~f:(fun(ic,oc)->copy_channelsicoc)~finally:close_both|()->close_fds()|exceptionexn->close_fds();Exn.reraiseexn);;letcopyfile?chmod~src~dst()=letsrc_stats=matchUnix.statsrcwith|exceptionUnix.Unix_error(Unix.ENOENT,_,_)->letmessage=Printf.sprintf"%s: No such file or directory"srcinraise(Sys_errormessage)|{st_kind=S_DIR;_}->raise(Sys_error"Is a directory")|stats->statsin(trycopyfilesrcdstwith|Unix.Unix_error(Unix.EPERM,"unlink",_)->letmessage=Printf.sprintf"%s: Is a directory"dstinraise(Sys_errormessage)|Unix.Unix_error(Unix.ENOENT,"realpath",_)->letmessage=Printf.sprintf"%s: No such file or directory"srcinraise(Sys_errormessage));matchchmodwith|None->()|Somechmod->src_stats.st_perm|>chmod|>Unix.chmoddst;;letcopy_file_portable?chmod~src~dst()=Exn.protectx(setup_copy?chmod~src~dst())~finally:close_both~f:(fun(ic,oc)->copy_channelsicoc);;letcopy_file_best=matchavailablewith|`Sendfile->sendfile_with_fallback|`Copyfile->copyfile|`Nothing->copy_file_portable;;letcopy_file_impl=ref`Bestletcopy_file?chmod~src~dst()=match!copy_file_implwith|`Portable->copy_file_portable?chmod~src~dst()|`Best->copy_file_best?chmod~src~dst();;endletset_copy_implm=Copyfile.copy_file_impl:=mmoduleMake(Path:sigtypetvalto_string:t->stringend)=structtypepath=Path.tletopen_in?(binary=true)p=letfn=Path.to_stringpinifbinarythenStdlib.open_in_binfnelseStdlib.open_infn;;letdefault_out_perm=0o666letopen_out?(binary=true)?(perm=default_out_perm)p=letfn=Path.to_stringpinletflags:Stdlib.open_flaglist=[Open_wronly;Open_creat;Open_trunc;(ifbinarythenOpen_binaryelseOpen_text)]inStdlib.open_out_genflagspermfn;;letwith_file_in?binaryfn~f=Exn.protectx(open_in?binaryfn)~finally:close_in~fletwith_file_out?binary?permp~f=Exn.protectx(open_out?binary?permp)~finally:close_out~f;;letwith_lexbuf_from_filefn~f=with_file_infn~f:(funic->letlb=Lexing.from_channelicinlb.lex_curr_p<-{pos_fname=Path.to_stringfn;pos_lnum=1;pos_bol=0;pos_cnum=0};flb);;letread_file_chan?binaryfn=matchwith_file_infn~f:Fs_io.read_all_unless_large?binarywith|Okx->x|Errorexn->raiseexn;;letread_file?(binary=true)fn=ifbinarythenFs_io.read_file(Path.to_stringfn)|>Result.ok_exnelseread_file_chan~binaryfn;;letlines_of_filefn=with_file_infn~f:input_lines~binary:falseletzero_strings_of_filefn=with_file_infn~f:input_zero_separated~binary:trueletwrite_file?(binary=true)?permfndata=ifbinarythenFs_io.write_file~perm:(Option.value~default:default_out_permperm)~data~path:(Path.to_stringfn)|>Result.ok_exnelsewith_file_out~binary?permfn~f:(funoc->output_stringocdata);;letwrite_lines?binary?permfnlines=with_file_out?binary?permfn~f:(funoc->List.iter~f:(funline->output_stringocline;output_stringoc"\n")lines);;letread_file_and_normalize_eolsfn=ifnotStdlib.Sys.win32thenread_filefnelse(letsrc=read_filefninletlen=String.lengthsrcinletdst=Bytes.createleninletrecfind_next_crnli=matchString.index_fromsrci'\r'with|None->None|Somej->ifj+1<len&&src.[j+1]='\n'thenSomejelsefind_next_crnl(j+1)inletrecloopsrc_posdst_pos=matchfind_next_crnlsrc_poswith|None->letlen=iflen>src_pos&&src.[len-1]='\r'thenlen-1-src_poselselen-src_posinBytes.blit_string~src~src_pos~dst~dst_pos~len;Bytes.sub_stringdst~pos:0~len:(dst_pos+len)|Somei->letlen=i-src_posinBytes.blit_string~src~src_pos~dst~dst_pos~len;letdst_pos=dst_pos+leninBytes.setdstdst_pos'\n';loop(i+2)(dst_pos+1)inloop00);;letcompare_text_filesfn1fn2=lets1=read_file_and_normalize_eolsfn1inlets2=read_file_and_normalize_eolsfn2inString.compares1s2;;letcompare_filesfn1fn2=lets1=read_filefn1inlets2=read_filefn2inString.compares1s2;;letsetup_copy?chmod~src~dst()=letsrc=Path.to_stringsrcinletdst=Path.to_stringdstinsetup_copy?chmod~src~dst();;letcopy_file?chmod~src~dst()=letsrc=Path.to_stringsrcinletdst=Path.to_stringdstinCopyfile.copy_file?chmod~src~dst();;letfile_linepathn=with_file_in~binary:falsepath~f:(funic->for_=1ton-1doignore(input_lineic)done;input_lineic);;letfile_linespath~start~stop=with_file_in~binary:truepath~f:(funic->letrecauxacclnum=iflnum>stopthenList.revaccelseiflnum<startthen(ignore(input_lineic);auxacc(lnum+1))else(letline=input_lineicinaux((string_of_intlnum,line)::acc)(lnum+1))inaux[]1);;letcat?binary?dstfn=letdst=matchdstwith|Somedst->dst|None->stdoutinwith_file_in?binaryfn~f:(funic->copy_channelsicdst);;endincludeMake(Path)moduleString_path=structincludeMake(structtypet=stringletto_stringx=xend)letcopy_file=Copyfile.copyfileendletportable_symlink~src~dst=ifStdlib.Sys.win32thencopy_file~src~dst()else(letsrc=matchPath.parentdstwith|None->Path.to_stringsrc|Somefrom->Path.reach~fromsrcinletdst=Path.to_stringdstinmatchUnix.readlinkdstwith|target->iftarget<>srcthen((* @@DRA Win32 remove read-only attribute needed when symlinking
enabled *)Unix.unlinkdst;Unix.symlinksrcdst)|exceptionUnix.Unix_error_->Unix.symlinksrcdst);;letportable_hardlink~src~dst=letuser_errormsg=User_error.raise[Pp.textf"Sandbox creation error: cannot resolve symbolic link %S."(Path.to_stringsrc);Pp.textf"Reason: %s"msg]in(* CR-someday amokhov: Instead of always falling back to copying, we could
detect if hardlinking works on Windows and if yes, use it. We do this in
the Dune cache implementation, so we can share some code. *)matchStdlib.Sys.win32with|true->copy_file~src~dst()|false->letsrc=matchFpath.follow_symlink(Path.to_stringsrc)with|Okpath->Path.of_stringpath|ErrorNot_a_symlink->src|ErrorMax_depth_exceeded->user_error"Too many indirections; is this a cyclic symbolic link?"|Error(Unix_errorerror)->user_error(Unix_error.Detailed.to_string_humerror)in(tryPath.linksrcdstwith|Unix.Unix_error(Unix.EEXIST,_,_)->(* CR-someday amokhov: Investigate why we need to occasionally clear the
destination (we also do this in the symlink case above). Perhaps, the
list of dependencies may have duplicates? If yes, it may be better to
filter out the duplicates first. *)Path.unlink_exndst;Path.linksrcdst|Unix.Unix_error(Unix.EMLINK,_,_)->(* If we can't make a new hard link because we reached the limit on the
number of hard links per file, we fall back to copying. We expect
that this happens very rarely (probably only for empty files). *)copy_file~src~dst());;