Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source
Source file diskuvbox.ml
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400(******************************************************************************)(* Copyright 2022 Diskuv, Inc. *)(* *)(* Licensed under the Apache License, Version 2.0 (the "License"); *)(* you may not use this file except in compliance with the License. *)(* You may obtain a copy of the License at *)(* *)(* http://www.apache.org/licenses/LICENSE-2.0 *)(* *)(* Unless required by applicable law or agreed to in writing, software *)(* distributed under the License is distributed on an "AS IS" BASIS, *)(* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *)(* See the License for the specific language governing permissions and *)(* limitations under the License. *)(******************************************************************************)openBostypebox_error=string->stringtypewalk_path=Root|FileofFpath.t|DirectoryofFpath.ttypepath_attribute=First_in_directory|Last_in_directory[@@derivingord]modulePath_attributes=Set.Make(structtypet=path_attributeletcompare=compare_path_attributeend)(* Error Handling *)letrresult_error_to_string~errmsg=err(Fmt.str"%a"Rresult.R.pp_msgmsg)letmap_rresult_error_to_string~err=function|Okv->Result.Okv|Errormsg->Result.Error(rresult_error_to_string~errmsg)letmap_string_to_rresult_error=function|Okv->Result.Okv|Errors->Rresult.R.error_msgsmoduletypeERROR_HANDLER=sigvalbox_error:box_errorendmoduleMonad_syntax_rresult(Error_handler:ERROR_HANDLER)=structlet(let*)r(f:'a->('c,'b)result)=Rresult.R.bindr(funa->matchfawith|Okv->Okv|Errormsg->Rresult.R.error_msg(Error_handler.box_error(rresult_error_to_string~err:Fun.idmsg)))let(let+)xf=Rresult.R.mapfxendletdir_dot=Fpath.v"."(** {1 Windows 260 character limit friendly functions}
Any failures with these functions will tell you to look at the 260
character limit as an explanation. *)letwindows_max_path=260(** [bos_tmp_name_max] is the maximum length of the basename of
a temporary file created by the Opam/findlib package ["bos"]. *)letbos_tmp_name_max=String.length"bos-837f7c.tmp"letdirsep_length=String.lengthFpath.dir_sep(** [has_windows_path_problem file] gives true if either the length of [file]
exceeds the Windows maximum {!windows_max_path} or if a temporary file
created by the Opam/findlib package ["bos"] in the directory of [file]
would exceed the Windows maximum {!windows_max_path} *)lethas_windows_path_problemfile=Sys.win32&&(String.length(Fpath.to_stringfile)>=windows_max_path||String.length(Fpath.to_string(Fpath.parentfile))+dirsep_length+bos_tmp_name_max>=windows_max_path)letfriendly_write_opffile=matchf()with|Okv->Okv|Errormwhenhas_windows_path_problemfile->Rresult.R.(error_msg(Fmt.str"We recommend that you rename your directories to be smaller \
because there was a failure writing to the pathname %a. It is \
likely caused by that pathname (or a temporary filename like \
bos-837f7c.tmp in the same directory) exceeding the default \
Windows %d character pathname limit. It may also be what the \
system reported: %a."Fpath.ppfilewindows_max_pathpp_msgm))|Errormsg->Errormsg(** Small strings only; maximum string is 16MiB for 32-bit OCaml.
Confer: https://ocamlverse.github.io/content/runtime.html *)letfriendly_write_small_string?modefilecontent=friendly_write_op(fun()->OS.File.write?modefilecontent)fileletfriendly_copyfile?mode?(bufsize=1_048_576)~err~src~dst()=letopenMonad_syntax_rresult(structletbox_error=errend)inletwrite_file_contents~output=letrechelperinput=matchinput()with|Some(b,pos,len)->output(Some(b,pos,len));helperinput|None->()inletbuffer=Bytes.createbufsizeinOS.File.with_input~bytes:buffersrc(funinput()->helperinput)()in(* Copy file using buffered copy *)let*nested_result=friendly_write_op(fun()->let*()=(* For Windows, can't write without turning off read-only flag.
In fact, you can still get Permission Denied even after turning
off read-only flag, perhaps because Windows has a richer
permissions model than POSIX. So we remove the file
after turning off read-only *)ifSys.win32thenlet*exists=OS.File.existsdstinifexiststhenlet*()=OS.Path.Mode.setdst0o644inOS.File.deletedstelseOk()elseOk()inOS.File.with_output?modedst(funoutput()->write_file_contents~output)())dstinnested_result(* Public Functions *)letcurrent_directory?(err=Fun.id)()=map_rresult_error_to_string~err(OS.Dir.current())letabsolute_path?(err=Fun.id)fp=ifFpath.is_absfpthenResult.Ok(Fpath.normalizefp)elsematchcurrent_directory~err()with|Okpwd->Result.OkFpath.(normalize(pwd//fp))|Errore->Erroreletwalk_down?(err=Fun.id)?(max_depth=0)~from_path~f()=letopenMonad_syntax_rresult(structletbox_error=errend)inletrecwalkwalk_pathpath_on_fspath_attributesdepth=(* pre-order traversal: visit the path first *)let*()=map_string_to_rresult_error(f~depth~path_attributeswalk_path)inlet*path_is_dir,child_pathize=matchwalk_pathwith|Root->let*dir_exists=OS.Dir.existspath_on_fsinOk(dir_exists,funchild->child)|Filerelpath->letraise_err_child=failwith(Fmt.str"Should be impossible to descend below the File %a. Started \
from %a and got to %a"Fpath.pprelpathFpath.ppfrom_pathFpath.pppath_on_fs)inOk(false,raise_err)|Directoryrelpath->Ok(true,funchild->Fpath.(relpath//child))inmatchpath_is_dirwith|true->ifdepth<max_depththen(* pre-order traversal: descend last *)letrecsiblings~first=function|[]->Ok()|hd::tl->letchild_path_attributes=match(first,tl=[])with|false,true->Path_attributes.of_list[Last_in_directory]|true,true->Path_attributes.of_list[First_in_directory;Last_in_directory]|true,_->Path_attributes.of_list[First_in_directory]|_->Path_attributes.emptyinletchild_path_on_fs=Fpath.(path_on_fs//hd)inlet*child_dir_exists=OS.Dir.existschild_path_on_fsinlet*()=matchchild_dir_existswith|true->walk(Directory(child_pathizehd))child_path_on_fschild_path_attributes(depth+1)|false->walk(File(child_pathizehd))child_path_on_fschild_path_attributes(depth+1)insiblings~first:falsetlinlet*dir_entries=OS.Dir.contents~rel:truepath_on_fsinletsorted_dir_entries=List.sortFpath.comparedir_entriesinlet*()=siblings~first:truesorted_dir_entriesinOk()elseOk()|false->Ok()inmap_rresult_error_to_string~err(let*from_path=OS.Path.must_existfrom_pathinwalkRootfrom_pathPath_attributes.empty0)letfind_up?(err=Fun.id)?(max_ascent=20)~from_dir~basenames()=letopenMonad_syntax_rresult(structletbox_error=errend)inletrecvalidate=function|[]->Ok()|hd::tl->(letbasename_norm=Fpath.normalizehdinmatchList.length(Fpath.segsbasename_norm)with|1->validatetl|0->Rresult.R.error_msgf"No basename can be empty. The find-up search was given the \
following basenames: %a"Fmt.(Dump.listFpath.pp)basenames|_->Rresult.R.error_msgf"Basenames cannot have directory separators. The find-up search \
was given the invalid basename: %a"Fpath.pphd)inletrecsearchpathbasenames_remainingascents_remaining=ifascents_remaining<=0||Fpath.is_rootpaththenOkNoneelsematchbasenames_remainingwith|[]->letbasedir,_rel=Fpath.split_basepathinsearchbasedirbasenames(ascents_remaining-1)|hd::tl->letcandidate=Fpath.(path//hd)inlet*exists=OS.File.existscandidateinifexiststhenOk(Somecandidate)elsesearchpathtlascents_remaininginmap_rresult_error_to_string~err(let*()=validatebasenamesinlet*from_dir=OS.Dir.must_existfrom_dirinsearch(Fpath.normalizefrom_dir)(List.mapFpath.normalizebasenames)max_ascent)lettouch_file?(err=Fun.id)~file()=letopenMonad_syntax_rresult(structletbox_error=errend)inmap_rresult_error_to_string~err(letparent_file=Fpath.parentfileinlet*created=OS.Dir.createparent_fileinifcreatedthenLogs.debug(funl->l"[touch_file] Created directory %a"Fpath.ppparent_file);let*exists=OS.File.existsfileinifexiststhen(* Modify access and modification times to the current time (0.0). *)Ok(Unix.utimes(Fpath.to_stringfile)0.00.0)else(* Write empty file *)friendly_write_small_string~mode:0o644file"")letrewrite_dst?basename_rewriter~dst()=let(let*)=Result.bindinletdst_dir,dst_basename=Fpath.(split_base(normalizedst))inlet*dst_basename'=matchbasename_rewriterwith|Somerw->rw(Fpath.to_stringdst_basename)|>Fpath.of_string|None->Okdst_basenameinOkFpath.(dst_dir//dst_basename')letcopy_file?(err=Fun.id)?bufsize?mode?basename_rewriter~src~dst()=letopenMonad_syntax_rresult(structletbox_error=errend)inmap_rresult_error_to_string~err(let*src=OS.File.must_existsrcinlet*mode=matchmodewithSomem->Okm|None->OS.Path.Mode.getsrcinlet*dst=rewrite_dst?basename_rewriter~dst()inletparent_dst=Fpath.parentdstinlet*created=OS.Dir.createparent_dstinifcreatedthenLogs.debug(funl->l"[copy_file] Created directory %a"Fpath.ppparent_dst);friendly_copyfile?bufsize~mode~err~src~dst())letcopy_dir?(err=Fun.id)?bufsize?basename_rewriter~src~dst()=letopenMonad_syntax_rresult(structletbox_error=errend)inletdo_copy_dir~src~dst=letraise_fold_errorfpathresult=Rresult.R.error_msgf"@[[copy_dir] A copy directory operation errored out while visiting \
%a.@]@,\
@[ @[%a@]@]"Fpath.ppfpath(Rresult.R.pp~ok:(Fmt.any"<unknown copy_dir problem>")~error:Rresult.R.pp_msg)resultinletcprel=function|Error_ase->(* no more copying if we had an error *)e|Ok()->(let*rel=match(Fpath.equalsrcrel,Fpath.relativize~root:srcrel)with|true,_->Okdir_dot|false,Somer->Okr|false,None->Rresult.R.error_msg(Fmt.str"During copy found a path %a that was not a subpath of \
the source directory %a"Fpath.pprelFpath.ppsrc)inletsrc=Fpath.(normalize(src//rel))anddst=Fpath.(normalize(dst//rel))inlet*dst=rewrite_dst?basename_rewriter~dst()inlet*isdir=OS.Dir.existssrcinmatchisdirwith|true->let+created=OS.Dir.createdstinifcreatedthenLogs.debug(funl->l"[copy_dir] Created directory %a"Fpath.ppdst);()|false->let*mode=OS.Path.Mode.getsrcinletparent_dst=Fpath.parentdstinlet*created=OS.Dir.createparent_dstinifcreatedthenLogs.debug(funl->l"[copy_dir] Created directory %a"Fpath.ppparent_dst);let*()=ifSys.win32then((* Avoid the error:
rename Z:\\source\\dkml-install-api\\_opam\\.opam-switch\\build\\dkml-installer-network-ocaml.0.4.0\\_build\\installer-work\\archive\\generic\\staging\\staging-unixutils\\generic\\bos-7a2f24.tmp to Z:\\source\\dkml-install-api\\_opam\\.opam-switch\\build\\dkml-installer-network-ocaml.0.4.0\\_build\\installer-work\\archive\\generic\\staging\\staging-unixutils\\generic\\unix_install.bc.exe: Permission denied
Windows does not allow renames if the target file exists.
But if we simply delete it we get the true error:
delete file Z:\\source\\dkml-install-api\\_opam\\.opam-switch\\build\\dkml-installer-network-ocaml.0.4.0\\_build\\installer-work\\archive\\generic\\staging\\staging-unixutils\\generic\\unix_install.bc.exe: Permission denied
which has permissions:
-r-xr-xr-x
So bos.0.2.1 is probably trying to delete but not checking
for success, or not deleting at all. Either way it needs
a chmod. Need to upstream a fix with bos.0.2.1 or perhaps
Stdlib!
*)let*exists=OS.File.existsdstinifexiststhenUnix.chmod(Fpath.to_stringdst)0o644;Ok())elseOk()inlet+()=friendly_copyfile?bufsize~err~mode~src~dst()in())inlet*folds=OS.Path.fold~err:raise_fold_error~dotfiles:truecp(Result.Ok())[src]inmatchfoldswith|Ok()->Result.Ok()|Errormsg->Rresult.R.error_msg(Fmt.str"@[[copy_dir] @[Failed to copy the directory@]@[@ from %a@]@[@ to \
%a@]@]@ @[(%a)@]"Fpath.ppsrcFpath.ppdstRresult.R.pp_msgmsg)inmap_rresult_error_to_string~err(let*src=OS.Dir.must_existsrcinlet*abs_src=map_string_to_rresult_error(absolute_pathsrc)inlet*abs_dst=map_string_to_rresult_error(absolute_pathdst)indo_copy_dir~src:abs_src~dst:abs_dst)