123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137moduleExn=structletprotectxx~f~finally=matchfxwith|y->finallyx;y|exceptione->finallyx;raisee;;endmoduleBytes=BytesLabelsletreceagerly_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;;letwith_file_infn~f=Exn.protectx(Stdlib.open_in_binfn)~finally:close_in~flettoo_big=Failure"file is too large"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->Errortoo_big|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);;letread_file_chanfn=with_file_infn~f:read_all_unless_largeletread_all_fd=letrecreadfdbufposleft=ifleft=0then`Okelse(matchUnix.readfdbufposleftwith|0->`Eof|n->readfdbuf(pos+n)(left-n))infunfd->matchUnix.fstatfdwith|exceptionUnix.Unix_error(e,x,y)->Error(`Unix(e,x,y))|{Unix.st_size;_}->ifst_size=0thenOk""elseifst_size>Sys.max_string_lengththenError`Too_bigelse(letb=Bytes.createst_sizeinmatchreadfdb0st_sizewith|exceptionUnix.Unix_error(e,x,y)->Error(`Unix(e,x,y))|`Eof->Error`Retry|`Ok->Ok(Bytes.unsafe_to_stringb));;letwith_file_in_fdfn~f=Exn.protectx(Unix.openfilefn[O_RDONLY;O_CLOEXEC]0)~f~finally:Unix.close;;letread_filefn=with_file_in_fdfn~f:(funfd->matchread_all_fdfdwith|Oks->Oks|Error`Retry->read_file_chanfn|Error`Too_big->Errortoo_big|Error(`Unix(e,s,x))->Error(Unix.Unix_error(e,s,x)));;letwrite_file=letrecwritefdstrposleft=ifleft>0then(letwritten=Unix.single_write_substringfdstrposleftinwritefdstr(pos+written)(left-written))infun~perm~path~data->matchUnix.openfilepath[O_WRONLY;O_CLOEXEC;O_CREAT;O_TRUNC]perm|>Exn.protectx~finally:Unix.close~f:(funfd->writefddata0(String.lengthdata))with|exceptionexn->Errorexn|()->Ok();;