123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532moduleStable0=structopenCore.Core_stablemoduleInet_port=structmoduleV1=structmoduleT=structtypet=int[@@derivingcompare,equal,hash]letof_int_exnx=ifx>0&&x<65536thenxelsefailwith(Core.sprintf"%d is not a valid port number."x);;letto_intx=xincludeSexpable.Of_sexpable.V1(Int.V1)(structtypenonrect=tletof_sexpable=of_int_exnletto_sexpable=to_intend)includeBinable.Of_binable.V1[@alert"-legacy"](Int.V1)(structtypenonrect=tletof_binable=of_int_exnletto_binable=to_intend)include(valComparator.V1.make~compare~sexp_of_t)let%expect_test_=print_string[%bin_digest:t];[%expect{| 698cfa4093fe5e51523842d37b92aeac |}];;endincludeTincludeComparable.V1.Make(T)endendendopenCoreopenPolyopenUnixexternalraw_fork_exec:stdin:File_descr.t->stdout:File_descr.t->stderr:File_descr.t->?working_dir:string->?setuid:int->?setgid:int->?env:stringarray->string->stringarray->Pid.t="extended_ml_spawn_bc""extended_ml_spawn"letraw_fork_exec~stdin~stdout~stderr?working_dir?setuid?setgid?envprogargv=(* [spawn] is generally preferred: it seems better tested and more actively maintained.
It also uses [vfork] so it's more efficient. For now we still must fall back to
[extended_ml_spawn] for the case when [setuid] or [setgid] is requested,
but we should completely switch to [spawn] when/if it supports that. *)matchsetuid,setgidwith|None,None->letenv=Option.map~f:(funenv->Spawn.Env.of_list(Array.to_listenv))envinletcwd=Option.value_map~default:Spawn.Working_dir.Inherit~f:(funcwd->Pathcwd)working_dirinletargv=Array.to_listargvinPid.of_int(Spawn.spawn?env~cwd~prog~argv~stdin~stdout~stderr())|Some_,_|_,Some_->raw_fork_exec~stdin~stdout~stderr?working_dir?setuid?setgid?envprogargv;;moduleEnv=structopenString.Maptypet=stringString.Map.tletempty:t=emptyletget()=Array.fold(Unix.environment())~init:empty~f:(funenvstr->matchString.lsplit2~on:'='strwith|Some(key,data)->set~key~dataenv|None->failwithf"extended_unix.Env.get %S is not in the form of key=value"str());;letadd~key~dataenv=ifString.memkey'='thenfailwithf"extended_unix.Env.add:variable to export in the environment %S contains an \
equal sign"key()elseifString.memkey'\000'thenfailwithf"extended_unix.Env.add:variable to export in the environment %S contains an \
null character"key()elseifString.memdata'\000'thenfailwithf"extended_unix.Env.add:value (%S) to export in the environment for %S contains \
an null character"datakey()elseString.Map.set~key~dataenv;;letto_string_arrayenv=String.Map.to_alistenv|>List.map~f:(fun(k,v)->k^"="^v)|>List.to_array;;endletfork_exec?(stdin=Unix.stdin)?(stdout=Unix.stdout)?(stderr=Unix.stderr)?(path_lookup=true)?env?working_dir?setuid?setgidprogargs=letenv=Option.mapenv~f:(fune->letinit,l=matchewith|`Extendl->Env.get(),l|`Replacel->Env.empty,linList.fold_leftl~init~f:(funenv(key,data)->Env.add~key~dataenv)|>Env.to_string_array)andfull_prog=ifpath_lookupthen(matchShell_internal.whichprogwith|Somes->s|None->failwithf"fork_exec: Process not found %s"prog())elseproginraw_fork_exec~stdin~stdout~stderr?working_dir?setuid?setgid?envfull_prog(Array.of_list(prog::args));;externalseteuid:int->unit="extended_ml_seteuid"externalsetreuid:uid:int->euid:int->unit="extended_ml_setreuid"externalhtonl:Int32.t->Int32.t="extended_ml_htonl"externalntohl:Int32.t->Int32.t="extended_ml_ntohl"let%test_=htonl(ntohl0xdeadbeefl)=0xdeadbeefltypestatvfs={bsize:int(** file system block size *);frsize:int(** fragment size *);blocks:int(** size of fs in frsize units *);bfree:int(** # free blocks *);bavail:int(** # free blocks for non-root *);files:int(** # inodes *);ffree:int(** # free inodes *);favail:int(** # free inodes for non-root *);fsid:int(** file system ID *);flag:int(** mount flags *);namemax:int(** maximum filename length *)}[@@derivingsexp,bin_io](** get file system statistics *)externalstatvfs:string->statvfs="statvfs_stub"(** get load averages *)externalgetloadavg:unit->float*float*float="getloadavg_stub"moduleExtended_passwd=structopenPasswdletof_passwd_line_exns=matchString.splits~on:':'with|[name;passwd;uid;gid;gecos;dir;shell]->{name;passwd;uid=Int.of_stringuid;gid=Int.of_stringgid;gecos;dir;shell}|_->failwithf"of_passwd_line: failed to parse: %s"s();;letof_passwd_lines=Option.try_with(fun()->of_passwd_line_exns)letof_passwd_file_exnfn=Exn.protectx(In_channel.createfn)~f:(funchan->List.map(In_channel.input_lineschan)~f:of_passwd_line_exn)~finally:In_channel.close;;letof_passwd_filef=Option.try_with(fun()->of_passwd_file_exnf)endletstrptime=Core.Unix.strptimemoduleInet_port=structmoduleStable=Stable0.Inet_portmoduleT=structtypet=int[@@derivingcompare,equal,hash]typecomparator_witness=Stable.V1.comparator_witnessletcomparator=Stable.V1.comparatorletsexp_of_t=Stable.V1.sexp_of_tendincludeTletof_int_exn=Stable.V1.of_int_exnletof_intx=trySome(of_int_exnx)with|_->None;;letof_string_exnx=Int.of_stringx|>of_int_exnletof_stringx=trySome(of_string_exnx)with|_->None;;letto_stringx=Int.to_stringxletto_intx=xletarg_type=Command.Spec.Arg_type.createof_string_exnincludeComparable.Make_plain_using_comparator(T)endlet%test_=Inet_port.of_string"88"=Some88let%test_=Inet_port.of_string"2378472398572"=Nonelet%test_=Inet_port.of_int88=Some88let%test_=Inet_port.of_int872342=NonemoduleMac_address=struct(* An efficient internal representation would be something like a 6 byte array,
but let's use a hex string to get this off the ground. *)moduleT=structtypet=string[@@derivingsexp,bin_io,compare,hash]let(=)=String.(=)letequal=(=)letof_strings=letaddr=String.lowercases|>String.filter~f:(function|'a'..'f'|'0'..'9'->true|_->false)inletlength=String.lengthaddriniflength<>12thenfailwithf"MAC address '%s' has the wrong length: %d"slength();addr;;letto_stringt=letrecloopacc=function|a::b::rest->letx=String.of_char_list[a;b]inloop(x::acc)rest|[]->List.revacc|>String.concat~sep:":"|_->assertfalseinloop[](String.to_listt);;letto_string_ciscot=letlst=String.to_listtinleta=List.takelst4|>String.of_char_listandb=List.take(List.droplst4)4|>String.of_char_listandc=List.droplst8|>String.of_char_listinString.concat~sep:"."[a;b;c];;lett_of_sexpsexp=String.t_of_sexpsexp|>of_stringletsexp_of_tt=to_stringt|>String.sexp_of_tlet_flag=Command.Spec.Arg_type.createof_stringendincludeTincludeHashable.Make(T)endlet%test_=Mac_address.to_string(Mac_address.of_string"00:1d:09:68:82:0f")="00:1d:09:68:82:0f";;let%test_=Mac_address.to_string(Mac_address.of_string"00-1d-09-68-82-0f")="00:1d:09:68:82:0f";;let%test_=Mac_address.to_string(Mac_address.of_string"001d.0968.820f")="00:1d:09:68:82:0f";;let%test_=Mac_address.to_string_cisco(Mac_address.of_string"00-1d-09-68-82-0f")="001d.0968.820f";;moduleQuota=structtypebytes=Int63.t[@@derivingsexp]typeinodes=Int63.t[@@derivingsexp]letbytesx=xletinodesx=xtype'unitslimit={soft:'unitsoption[@sexp.option];hard:'unitsoption[@sexp.option];grace:Time.toption[@sexp.option]}[@@derivingsexp]type'unitsusage=private'units(* None is encoded as zero *)type'unitsc_limit={c_soft:'units;c_hard:'units;c_grace:Time.t}letzero_bytes=bytesInt63.zeroletzero_inodes=inodesInt63.zeroletml_limit_of_c_limit~zero{c_soft;c_hard;c_grace}={soft=(ifc_soft=zerothenNoneelseSomec_soft);hard=(ifc_hard=zerothenNoneelseSomec_hard);grace=(ifc_grace=Time.epochthenNoneelseSomec_grace)};;letc_limit_of_ml_limit~zero{soft;hard;grace}={c_soft=(matchsoftwith|None->zero|Somex->x);c_hard=(matchhardwith|None->zero|Somex->x);c_grace=(matchgracewith|None->Time.epoch|Somex->x)};;externalquota_query:[`User|`Group]->id:int->path:string->bytesc_limit*bytesusage*inodesc_limit*inodesusage="quota_query"externalquota_modify:[`User|`Group]->id:int->path:string->bytesc_limit->inodesc_limit->unit="quota_modify"letqueryuser_or_group~id~path=tryletblimit,busage,ilimit,iusage=quota_queryuser_or_group~id~pathinOk(ml_limit_of_c_limit~zero:zero_bytesblimit,busage,ml_limit_of_c_limit~zero:zero_inodesilimit,iusage)with|Unix.Unix_error_asexn->Or_error.of_exnexn;;letsetuser_or_group~id~pathbyte_limitinode_limit=tryOk(quota_modifyuser_or_group~id~path(c_limit_of_ml_limit~zero:zero_bytesbyte_limit)(c_limit_of_ml_limit~zero:zero_inodesinode_limit))with|Unix.Unix_error_asexn->Or_error.of_exnexn;;endmoduleMount_entry=struct(* see: man 3 getmntent *)typet={fsname:string;directory:string;fstype:string;options:string;dump_freq:intoption[@sexp.option];fsck_pass:intoption[@sexp.option]}[@@derivingsexp,fields]letescape_seqs=["040"," ";"011","\t";"012","\n";"134","\\";"\\","\\"]letunescapes=letfind_and_drop_prefixs(prefix,replacement)=Option.map(String.chop_prefix~prefixs)~f:(funs->replacement,s)inletrecloops=matchString.lsplit2s~on:'\\'with|None->[s]|Some(l,r)->(matchList.find_mapescape_seqs~f:(find_and_drop_prefixr)with|None->l::"\\"::loopr|Some(x,r)->l::x::loopr)inString.concat(loops);;letparse_optional_ints=matchInt.of_stringswith|0->None|n->Somen;;letsplit_and_normalizeline=letinside_comment=reffalseinletwhitespace=' 'inString.mapline~f:(funx->ifChar.equalx'#'theninside_comment:=true;ifChar.is_whitespacex||!inside_commentthenwhitespaceelsex)|>String.split~on:whitespace|>List.filter~f:(funx->not(String.is_emptyx));;letparse_lineline=matchsplit_and_normalizeline|>List.map~f:unescapewith|[]->OkNone|fsname::directory::fstype::options::(([]|[_]|[_;_])asdump_freq_and_fsck_pass)->letdump_freq,fsck_pass=matchdump_freq_and_fsck_passwith|[]->None,None|[dump_freq]->Somedump_freq,None|[dump_freq;fsck_pass]->Somedump_freq,Somefsck_pass|_->assertfalseinOr_error.try_with(fun()->letdump_freq=Option.binddump_freq~f:parse_optional_intinletfsck_pass=Option.bindfsck_pass~f:parse_optional_intinifString.equalfstype"ignore"thenNoneelseSome{fsname;directory;fstype;options;dump_freq;fsck_pass})|_->Or_error.error"wrong number of fields"lineString.sexp_of_t;;letvisible_filesystemts=letadd_slash_if_neededs=ifString.is_suffixs~suffix:"/"thenselses^"/"inletoverlaymapt=letremove_prefix=add_slash_if_needed(directoryt)inletrecloopmap=matchString.Map.closest_keymap`Greater_thanremove_prefixwith|None->map|Some(key,_)->ifnot(String.is_prefix~prefix:remove_prefixkey)thenmapelseloop(String.Map.removemapkey)inString.Map.set(loopmap)~key:(directoryt)~data:tinList.foldts~init:String.Map.empty~f:(funmapt->ifnot(String.is_prefix~prefix:"/"(directoryt))thenmapelseoverlaymapt);;endletterminal_width=lazy((* When both stdout and stderr are not terminals, tput outputs 80 rather than the
number of columns, so we can't use [Process.run]. Instead, we use
[open_process_in] so that stderr is still the terminal. But, we don't want
tput's error messages to be sent to stderr and seen by the user, so we first
run tput with no output to see if it succeeds, and only then do we run it with
stderr not redirected. *)tryExn.protectx(Core.Unix.open_process_in"/usr/bin/tput cols &> /dev/null && /usr/bin/tput cols")~f:(funin_channel->In_channel.input_linein_channel|>Option.value_exn|>Int.of_string)~finally:In_channel.closewith|_->90);;