Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source
Source file dkml_install_api.ml
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378moduleArg=Cmdliner.ArgmoduleTerm=Cmdliner.TermmoduleCmd=Cmdliner.CmdmoduleContext=Types.ContextmoduleForward_progress=Forward_progressmoduletypeComponent_config=Dkml_install_api_intf.Component_configmoduletypeComponent_config_defaultable=Dkml_install_api_intf.Component_config_defaultableletadministrator=ifSys.win32then"Administrator privileges"else"root permissions"moduleDefault_component_config=structletinstall_depends_on=[]letuninstall_depends_on=[]letdo_nothing_with_ctx_t_ctx=()letsdocs=Cmdliner.Manpage.s_common_optionsletinstall_user_subcommand~component_name~subcommand_name~fl~ctx_t=letdoc=Fmt.str"Currently does nothing. Would install the component '%s' except the \
parts, if any, that need %s"component_nameadministratorinletinfo=Cmd.infosubcommand_name~sdocs~docinletcmd=Cmd.vinfoTerm.(constdo_nothing_with_ctx_t$ctx_t)inForward_progress.return(cmd,fl)letuninstall_user_subcommand~component_name~subcommand_name~fl~ctx_t=letdoc=Fmt.str"Currently does nothing. Would uninstall the component '%s' except the \
parts, if any, that need %s"component_nameadministratorinletinfo=Cmd.infosubcommand_name~sdocs~docinletcmd=Cmd.vinfoTerm.(constdo_nothing_with_ctx_t$ctx_t)inForward_progress.return(cmd,fl)letneeds_install_admin~ctx:(_:Context.t)=falseletneeds_uninstall_admin~ctx:(_:Context.t)=falseletinstall_admin_subcommand~component_name~subcommand_name~fl~ctx_t=letdoc=Fmt.str"Currently does nothing. Would install the parts of the component '%s' \
that need %s"component_nameadministratorinletinfo=Cmd.infosubcommand_name~sdocs~docinletcmd=Cmd.vinfoTerm.(constdo_nothing_with_ctx_t$ctx_t)inForward_progress.return(cmd,fl)letuninstall_admin_subcommand~component_name~subcommand_name~fl~ctx_t=letdoc=Fmt.str"Currently does nothing. Would uninstall the parts of the component \
'%s' that need %s"component_nameadministratorinletinfo=Cmd.infosubcommand_name~sdocs~docinletcmd=Cmd.vinfoTerm.(constdo_nothing_with_ctx_t$ctx_t)inForward_progress.return(cmd,fl)lettest()=()endmoduleLog_config=structincludeLog_configendletlog_spawn_onerror_exit~id?(success_exitcodes=funi->i==0)?conformant_subprocess_exitcodescmd=Logs.info(funm->m"Running command: %a"Bos.Cmd.ppcmd);letfl=Forward_progress.stderr_fatalloginletopenAstringinletsequence=let(let*)=Result.bindinlet*env=Bos.OS.Env.current()inletnew_env=letis_not_defined=matchString.Map.find"OCAMLRUNPARAM"envwith|None->true|Some""->true|Some_->falseinifis_not_definedthenString.Map.add"OCAMLRUNPARAM""b"envelseenvinBos.OS.Cmd.run_status~env:new_envcmdinmatchsequencewith|Ok(`Exitedspawned_exitcode)whensuccess_exitcodesspawned_exitcode->ifLogs.level()=SomeLogs.DebugthenLogs.info(funm->m"%a ran successfully with exit code %d"Bos.Cmd.ppcmdspawned_exitcode)elseLogs.info(funm->m"%a ran successfully with exit code %d"Fmt.(optionstring)(Bos.Cmd.line_toolcmd)spawned_exitcode);()|Ok(`Exitedspawned_exitcode)->letadjective,exitcode=ifconformant_subprocess_exitcodes=Somefalsethen("",Forward_progress.Exit_code.Exit_transient_failure)else("conformant ",List.fold_left(funaccec->ifspawned_exitcode=Forward_progress.Exit_code.to_int_exitcodeecthenecelseacc)Forward_progress.Exit_code.Exit_transient_failureForward_progress.Exit_code.values)infl~id(Fmt.str"%s\n\n\
Root cause: @[The %scommand had exit code %d:@ %a@]\n\n\
>>> %s <<<"(Forward_progress.Exit_code.to_short_sentenceexitcode)adjectivespawned_exitcodeBos.Cmd.ppcmd(Forward_progress.Exit_code.to_short_sentenceexitcode));exit(Forward_progress.Exit_code.to_int_exitcodeexitcode)|Ok(`Signaledc)->fl~id(Fmt.str"The command@ %a@ terminated from a signal %d"Bos.Cmd.ppcmdc);exit(Forward_progress.Exit_code.to_int_exitcodeExit_transient_failure)|Errorrmsg->fl~id(Fmt.str"The command@ %a@ could not be run due to: %a"Bos.Cmd.ppcmdRresult.R.pp_msgrmsg);exit(Forward_progress.Exit_code.to_int_exitcodeExit_transient_failure)moduleImmediate_fail(Id:sigvalid:stringend)=structlet(let*)rf=matchrwith|Okv->fv|Errors->Forward_progress.stderr_fatallog~id:Id.id(Fmt.str"%a"Rresult.R.pp_msgs);exit(Forward_progress.Exit_code.to_int_exitcodeExit_transient_failure)let(let+)fx=Rresult.R.mapxfendletchmod_plus_readwrite_dir~iddir=letopenImmediate_fail(structletid=idend)inletraise_fold_errorfpathresult=Rresult.R.error_msgf"@[A chmod u+rw directory operation errored out while visiting %a.@]@,\
@[ @[%a@]@]"Fpath.ppfpath(Rresult.R.pp~ok:(Fmt.any"<unknown rmdir problem>")~error:Rresult.R.pp_msg)resultinletchmod_u_rwrel=function|Error_ase->(* no more chmod if we had an error *)e|Ok()->letpath=Fpath.(dir//rel)inlet*mode=Bos.OS.Path.Mode.getpathinifmodeland0o600<>0o600thenlet+()=Bos.OS.Path.Mode.setpath(modelor0o600)in()elseOk()inlet*res=Bos.OS.Path.fold~err:raise_fold_errorchmod_u_rw(Ok())[dir]inmatchreswith|Ok()->Ok()|Errors->Rresult.R.error_msg(Fmt.str"@[@[Failed to chmod u+rw the directory@]@[@ %a@]@ .@]@ @[%a@]"Fpath.ppdirRresult.R.pp_msgs)(** [dos2unix s] converts all CRLF sequences in [s] into LF. Assumes [s] is ASCII encoded. *)letdos2unixs=letl=String.lengthsinString.to_seqis(* Shrink [\r\n] into [\n] *)|>Seq.filter_map(function|i,'\r'wheni+1<l&&s.[i+1]=='\n'->None|_,c->Somec)|>String.of_seqletstyled_stuck_infofmt=letpp1=Fmt.styled(`Fg`Magenta)fmtinletpp2=Fmt.styled(`Bg`Black)pp1inFmt.styled`Boldpp2letstyled_stuck_detailfmt=letpp1=Fmt.styled(`Fg`Red)fmtinletpp2=Fmt.styled(`Bg`Black)pp1inletpp3=Fmt.styled`Boldpp2inFmt.styled`Underlinepp3letuninstall_directory_onerror_exit~id~dir~wait_seconds_if_stuck=letopenImmediate_fail(structletid=idend)in(* On Windows we need to get write access before you can delete the
file. *)letfl=Forward_progress.stderr_fatalloginletsequence=let*exists=Bos.OS.Path.existsdirinifexiststhen(Logs.info(funm->m"Uninstalling directory: %a"Fpath.ppdir);let*()=chmod_plus_readwrite_dir~iddirin(*
OS.Dir.delete has bizarre error messages, like:
C:\Users\beckf\AppData\Local\Temp\build999583.dune\test_uninstall_7b4501\cmd.exe: The directory name is invalid.
when the above cmd.exe is being used. So we use cmd.exe on Windows instead which
has user-friendly DOS error messages.
*)match(Sys.win32,Bos.OS.Env.var"COMSPEC")with|true,Somecomspecwhencomspec!=""->(*
https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/rd
Example:
rd "C:\Temp\abc" /s /q
And instead of dealing with insane OCaml + DOS quoting issues, will
create a temporary batch file and execute that.
Other complexity is we won't get any error codes from `rd`. But we will get:
C:\Users\beckf\AppData\Local\Temp\f46f0508-df03-40e8-8661-728f1be41647\UninstallBlueGreenDeploy2\0\cmd.exe - Access is denied.
So any output on the error console indicates a problem.
*)letcmd=Printf.sprintf"@rd /s /q %s"(Filename.quote(Fpath.to_stringdir))inlet*batchfile=Bos.OS.File.tmp"rd_%s.bat"inlet*()=Bos.OS.File.writebatchfilecmdinletstart_secs=Unix.time()inletrechelper()=matchBos.OS.Cmd.run_out~err:Bos.OS.Cmd.err_run_outBos.Cmd.(vcomspec%"/c"%Fpath.to_stringbatchfile)|>Bos.OS.Cmd.out_stringwith|Ok("",(_,`Exited0))->Ok()|Ok(text,(_,`Exited0))->(* Exit 0 with any stdout/stderr is a problem. We used 'rd /q'
to suppress output, so any output is an error. *)letnow_secs=Unix.time()inletelapsed_secs=now_secs-.start_secsinifelapsed_secs>wait_seconds_if_stuckthenError(Rresult.R.msgf"The DOS command 'rd' did not succeed.@,@[<v>%a@]"Fmt.lines(dos2unixtext))else((* Retry until time complete *)Fmt.epr"@[<v>@,\
Stuck during uninstallation of %a@,\
Waited already %5.1f seconds; will wait at most %5.1f \
seconds.@,\
%a@,\
@[ %a@]@]@,\
@."Fpath.ppdirelapsed_secswait_seconds_if_stuck(styled_stuck_infoFmt.string)"Please stop and exit the program:"(styled_stuck_detailFmt.lines)(dos2unixtext);Unix.sleep5;helper())|Ok(text,(_,`Exitedv))->Error(Rresult.R.msgf"The DOS command DOS 'rd' exited with exit code %d.@,\
@[<v>%a@]"vFmt.lines(dos2unixtext))|Ok(text,(_,`Signaledv))->Error(Rresult.R.msgf"The DOS command DOS 'rd' was killed by signal %d.@,\
@[<v>%a@]"vFmt.lines(dos2unixtext))|Errormsg->Errormsginhelper()(*
let helper () =
match
Bos.OS.Cmd.run_out Bos.Cmd.(v comspec % "/c" % cmd)
|> Bos.OS.Cmd.out_string
with
| Ok ("", (_, `Exited 0)) -> Ok ()
| Ok (text, (_, `Exited 0)) ->
Error
(Rresult.R.msgf
"DOS 'rd' exited with exit code 0, but should not have \
produced output.@,\
@[<v>%a@]"
Fmt.lines (dos2unix text))
| Ok (text, (_, `Exited v)) ->
Error
(Rresult.R.msgf
"DOS 'rd' exited with exit code %d.@,@[<v>%a@]" v Fmt.lines
(dos2unix text))
| Ok (text, (_, `Signaled v)) ->
Error
(Rresult.R.msgf "DOS 'rd' killed by signal %d.@,@[<v>%a@]" v
Fmt.lines (dos2unix text))
| Error msg -> Error msg
in
helper ()
*)(* let ic =
Unix.open_process_args_in comspec
[| "/s"; "/c"; cmd |]
in
let rd_output = really_input_string ic 0 |> dos2unix in
match Unix.close_process_in ic with
| WEXITED 0 when rd_output = "" -> Ok ()
| WEXITED 0 ->
Error
(Rresult.R.msgf
"DOS 'rd' exited with exit code 0, but should not have \
produced output.@,\
@[<v>%a@]"
Fmt.lines rd_output)
| WEXITED v ->
Error
(Rresult.R.msgf "DOS 'rd' exited with exit code %d.@,@[<v>%a@]"
v Fmt.lines rd_output)
| WSIGNALED v ->
Error
(Rresult.R.msgf "DOS 'rd' killed by signal %d.@,@[<v>%a@]" v
Fmt.lines rd_output)
| WSTOPPED v ->
Error
(Rresult.R.msgf "DOS 'rd' stopped by signal %d.@,@[<v>%a@]" v
Fmt.lines rd_output))
*)(*
(match Unix.system cmd with
| WEXITED 0 -> Ok ()
| WEXITED v ->
Error (Rresult.R.msgf "DOS 'rd' exited with exit code %d" v)
| WSIGNALED v ->
Error (Rresult.R.msgf "DOS 'rd' killed by signal %d" v)
| WSTOPPED v ->
Error (Rresult.R.msgf "DOS 'rd' stopped by signal %d" v))
*)|_->Bos.OS.Dir.delete~recurse:truedir)elseOk()inmatchsequencewith|Ok()->()|Errorrmsg->fl~id(Fmt.str"The directory@ %a@ could not be uninstalled due to: %a"Fpath.ppdirRresult.R.pp_msgrmsg);exit(Forward_progress.Exit_code.to_int_exitcodeExit_transient_failure)