123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520letclose_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;;letopen_out?(binary=true)?(perm=0o666)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);;letreceagerly_input_accics~pos~lenacc=iflen<=0thenaccelse(letr=inputicsposleninifr=0thenaccelseeagerly_input_accics~pos:(pos+r)~len:(len-r)(acc+r));;(* [eagerly_input_string ic len] tries to read [len] chars from the channel.
Unlike [really_input_string], if the file ends before [len] characters are
found, it returns the characters it was able to read instead of raising an
exception.
This can be detected by checking that the length of the resulting string is
less than [len]. *)leteagerly_input_stringiclen=letbuf=Bytes.createleninletr=eagerly_input_accicbuf~pos:0~len0inifr=lenthenBytes.unsafe_to_stringbufelseBytes.sub_stringbuf~pos:0~len:r;;letread_all_unless_large=(* We use 65536 because that is the size of OCaml's IO buffers. *)letchunk_size=65536in(* Generic function for channels such that seeking is unsupported or
broken *)letread_all_generictbuffer=letrecloop()=Buffer.add_channelbuffertchunk_size;loop()intryloop()with|End_of_file->Ok(Buffer.contentsbuffer)infunt->(* Optimisation for regular files: if the channel supports seeking, we
compute the length of the file so that we read exactly what we need and
avoid an extra memory copy. We expect that most files Dune reads are
regular files so this optimizations seems worth it. *)matchin_channel_lengthtwith|exceptionSys_error_->read_all_generict(Buffer.createchunk_size)|nwhenn>Sys.max_string_length->Error()|n->(* For some files [in_channel_length] returns an invalid value. For
instance for files in /proc it returns [0] and on Windows the
returned value is larger than expected (it counts linebreaks as 2
chars, even in text mode).
To be robust in both directions, we: - use [eagerly_input_string]
instead of [really_input_string] in case we reach the end of the file
early - read one more character to make sure we did indeed reach the
end of the file *)lets=eagerly_input_stringtnin(matchinput_chartwith|exceptionEnd_of_file->Oks|c->(* The [+ chunk_size] is to make sure there is at least [chunk_size]
free space so that the first [Buffer.add_channel buffer t
chunk_size] in [read_all_generic] does not grow the buffer. *)letbuffer=Buffer.create(String.lengths+1+chunk_size)inBuffer.add_stringbuffers;Buffer.add_charbufferc;read_all_generictbuffer);;letpath_to_dynpath=String.to_dyn(Path.to_stringpath)letread_file?binaryfn=matchwith_file_infn~f:read_all_unless_large?binarywith|Okx->x|Error()->Code_error.raise"read_file: file is larger than Sys.max_string_length"["fn",path_to_dynfn];;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?permfndata=with_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=matchPath.follow_symlinksrcwith|Okpath->path|ErrorNot_a_symlink->src|ErrorMax_depth_exceeded->user_error"Too many indirections; is this a cyclic symbolic link?"|Error(Unix_errorerror)->user_error(Dune_filesystem_stubs.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());;