Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source
Source file protocol_9p_client.ml
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624(*
* Copyright (C) 2015 David Scott <dave.scott@unikernel.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*)openResultopenProtocol_9p_infixopenAstringmoduleError=Protocol_9p_erroropenErrormoduleTypes=Protocol_9p_typesmoduleRequest=Protocol_9p_requestmoduleResponse=Protocol_9p_responsemoduletypeS=sigtypetvalafter_disconnect:t->unitLwt.tvaldisconnect:t->unitLwt.tvalcreate:t->stringlist->string->Types.FileMode.t->unitError.tLwt.tvalwrite:t->stringlist->int64->Cstruct.t->unitProtocol_9p_error.tLwt.tvalread:t->stringlist->int64->int32->Cstruct.tlistError.tLwt.tvalmkdir:t->stringlist->string->Types.FileMode.t->unitError.tLwt.tvalremove:t->stringlist->unitError.tLwt.tvalreaddir:t->stringlist->Types.Stat.tlistError.tLwt.tvalstat:t->stringlist->Types.Stat.tError.tLwt.tmoduleKV_RO:Mirage_kv_lwt.ROwithtypet=tmoduleLowLevel:sigvalmaximum_write_payload:t->int32valallocate_fid:t->Protocol_9p_types.Fid.tError.tLwt.tvaldeallocate_fid:t->Protocol_9p_types.Fid.t->unitLwt.tvalwalk:t->Types.Fid.t->Types.Fid.t->stringlist->Response.Walk.tError.tLwt.tvalopenfid:t->Types.Fid.t->Types.OpenMode.t->Response.Open.tError.tLwt.tvalcreate:t->Types.Fid.t->?extension:string->string->Types.FileMode.t->Types.OpenMode.t->Response.Create.tError.tLwt.tvalstat:t->Types.Fid.t->Response.Stat.tError.tLwt.tvalwstat:t->Types.Fid.t->Types.Stat.t->Response.Wstat.tError.tLwt.tvalread:t->Types.Fid.t->int64->int32->Response.Read.tError.tLwt.tvalwrite:t->Types.Fid.t->int64->Cstruct.t->Response.Write.tError.tLwt.tvalclunk:t->Types.Fid.t->Response.Clunk.tError.tLwt.tvalremove:t->Types.Fid.t->Response.Remove.tError.tLwt.tvalupdate:t->?name:string->?length:int64->?mode:Types.FileMode.t->?mtime:int32->?gid:string->Types.Fid.t->unitError.tLwt.tendvalwalk_from_root:t->Types.Fid.t->stringlist->Response.Walk.tError.tLwt.tvalwith_fid:t->(Types.Fid.t->'aError.tLwt.t)->'aError.tLwt.tendmoduleMake(Log:Protocol_9p_s.LOG)(FLOW:Mirage_flow_lwt.S)=structmoduleReader=Protocol_9p_buffered9PReader.Make(Log)(FLOW)openLogtypefid=Types.Fid.ttypet={reader:Reader.t;writer:FLOW.flow;root:fid;msize:int32;upgraded:bool;(* 9P2000.u *)maximum_payload:int32;transmit_m:Lwt_mutex.t;mutableplease_shutdown:bool;shutdown_m:Lwt_mutex.t;shutdown_complete_t:unitLwt.t;mutablewakeners:Response.payloadError.tLwt.uTypes.Tag.Map.t;mutablefree_tags:Types.Tag.Set.t;free_tags_c:unitLwt_condition.t;max_fids:int32;mutablefids:Types.Fid.Set.t;free_fids_c:unitLwt_condition.t;}typeconnection=t(* For converting flow errors *)let(>>|=)mf=letopenLwtinm>>=function|Okx->fx|Error`Closed->return(error_msg"Writing to closed FLOW")|Errore->return(error_msg"Unexpected error on underlying FLOW: %a"FLOW.pp_write_errore)letread_one_packetreader=Reader.readreader>>*=funbuffer->Lwt.return(Response.readbuffer)>>*=fun(response,_)->debug(funf->f"S %a"Response.ppresponse);Lwt.return(Okresponse)letwrite_one_packetflowrequest=debug(funf->f"C %a"Request.pprequest);letsizeof=Request.sizeofrequestinletbuffer=Cstruct.createsizeofinLwt.return(Request.writerequestbuffer)>>*=fun_->FLOW.writeflowbuffer>>|=fun()->Lwt.return(Ok())letfinallyfg=letopenLwtinLwt.catch(fun()->f()>>=funok_or_error->g()>>=fun_ignore_error->returnok_or_error)(fune->g()>>=fun_ignore_error->faile)letdispatcher_is_runningt=Lwt.statet.shutdown_complete_t=Lwt.Sleepletrpctrequest:Response.payloadError.tLwt.t=(* Allocate a fresh tag, or wait if none are available yet *)letrecallocate_tag()=letopenLwtinift.free_tags=Types.Tag.Set.empty&&(dispatcher_is_runningt)thenLwt_condition.waitt.free_tags_c>>=fun()->allocate_tag()elseifnot(dispatcher_is_runningt)thenreturn(Error(`Msg"connection disconnected"))elselettag=Types.Tag.Set.min_eltt.free_tagsint.free_tags<-Types.Tag.Set.removetagt.free_tags;letth,wakener=Lwt.task()int.wakeners<-Types.Tag.Map.addtagwakenert.wakeners;return(Ok(tag,th))inletdeallocate_tagtag=t.free_tags<-Types.Tag.Set.addtagt.free_tags;(* The tag will have already been removed from the wakeners map *)Lwt_condition.signalt.free_tags_c();Lwt.return()inletwith_tagf=letopenLwtinallocate_tag()>>*=fun(tag,th)->finally(fun()->f(tag,th))(fun()->deallocate_tagtag)inwith_tag(fun(tag,th)->(* Lock the flow for output and transmit the packet *)Lwt_mutex.with_lockt.transmit_m(fun()->letrequest={Request.tag;payload=request}inwrite_one_packett.writerrequest)>>*=fun()->(* Wait for the response (or error) to be read *)th)(* The dispatcher thread reads responses from the FLOW and wakes up
the thread blocked in the rpc function. *)letrecdispatcher_tshutdown_complete_wakenert=ift.please_shutdownthenbeginLwt.return(Ok())endelseread_one_packett.reader>>*=funresponse->lettag=response.Response.taginifnot(Types.Tag.Map.memtagt.wakeners)thenbeginletpretty_printed=Sexplib.Sexp.to_string(Response.sexp_of_tresponse)inerr(funf->f"Received response with unexpected tag: %s"pretty_printed);dispatcher_tshutdown_complete_wakenertendelsebeginletwakener=Types.Tag.Map.findtagt.wakenersinLwt.wakeup_laterwakener(Okresponse.Response.payload);t.wakeners<-Types.Tag.Map.removetagt.wakeners;dispatcher_tshutdown_complete_wakenertendletreturn_error=function|Response.Err{Response.Err.ename}->Lwt.return(Error(`Msgename))|payload->Lwt.return(error_msg"Server sent unexpected reply: %s"(Sexplib.Sexp.to_string(Response.sexp_of_payloadpayload)))moduleLowLevel=structletmaximum_write_payloadt=t.maximum_payloadletflushtoldtag=rpctRequest.(Flush{Flush.oldtag})>>*=function|Response.Flushx->Lwt.return(Okx)|response->return_errorresponseletwalktfidnewfidwnames=rpctRequest.(Walk{Walk.fid;newfid;wnames})>>*=function|Response.Walkx->Lwt.return(Okx)|response->return_errorresponseletcreatetfid?extensionnamepermmode=rpctRequest.(Create{Create.fid;name;perm;mode;extension})>>*=function|Response.Createx->Lwt.return(Okx)|response->return_errorresponseletopenfidtfidmode=rpctRequest.(Open{Open.fid;mode})>>*=function|Response.Openx->Lwt.return(Okx)|response->return_errorresponseletstattfid=rpctRequest.(Stat{Stat.fid})>>*=function|Response.Statx->Lwt.return(Okx)|response->return_errorresponseletreadtfidoffsetcount=rpctRequest.(Read{Read.fid;offset;count})>>*=function|Response.Readx->Lwt.return(Okx)|response->return_errorresponseletwritetfidoffsetdata=rpctRequest.(Write{Write.fid;offset;data})>>*=function|Response.Writex->Lwt.return(Okx)|response->return_errorresponseletclunktfid=rpctRequest.(Clunk{Clunk.fid})>>*=function|Response.Clunkx->Lwt.return(Okx)|response->return_errorresponseletremovetfid=rpctRequest.(Remove{Remove.fid})>>*=function|Response.Removex->Lwt.return(Okx)|response->return_errorresponseletwstattfidstat=rpctRequest.(Wstat{Wstat.fid;stat})>>*=function|Response.Wstatx->Lwt.return(Okx)|response->return_errorresponseletupdatet?(name="")?(length=Types.Int64.any)?(mode=Types.FileMode.any)?(mtime=Types.Int32.any)?(gid="")fid=wstattfid{Types.Stat.name;length;mode;mtime;gid;(* It's illegal to set these *)ty=Types.Int16.any;dev=Types.Int32.any;qid=Types.Qid.any;atime=Types.Int32.any;uid="";muid="";u=None;}letversionreaderwritermsizeversion=write_one_packetwriter{Request.tag=Types.Tag.notag;payload=Request.VersionRequest.Version.({msize;version});}>>*=fun()->read_one_packetreader>>*=funresponse->matchresponsewith|{Response.payload=Response.Versionv}->Lwt.return(Okv)|{Response.payload=p}->return_errorpletattachreaderwriterfidafidunameanamen_uname=lettag=Types.Tag.Set.min_eltTypes.Tag.recommendedinwrite_one_packetwriter{Request.tag;payload=Request.AttachRequest.Attach.({fid;afid;uname;aname;n_uname})}>>*=fun()->read_one_packetreader>>*=funresponse->matchresponsewith|{Response.payload=Response.Attachx}->Lwt.return(Okx)|{Response.payload=p}->return_errorpletfid=function|0l->(* 0 is the pre-allocated FS root *)None|n->matchTypes.Fid.of_int32nwith|Okm->Somem|Error_->(* NOFID *)Noneletrecrandom_fid?(n=10)t=ifn=0then(Log.info(funl->l"Cannot allocate a new random fid after 10 tries");None)elsematchfid@@Random.int32Int32.max_intwith|None->random_fid~n:(n-1)t|Somefasr->ifTypes.Fid.Set.memft.fidsthenrandom_fidtelserletmin_fidt=matchfid@@Int32.predTypes.Fid.(to_int32@@Set.min_eltt.fids)with|None->random_fidt|Some_asr->r(* if max_fids is not reached, the allocation strategy is:
- pick max(allocated_fid) + 1
- if this is NOFID:
- pick min(allocated_fid) - 1
- if this is 0 or NOFID:
- pick a random fid until finding a non-allocated one
This means that keeping [0=(NOFID+1)] and [NOFID-1] always open
might be costly.*)letnext_fidt=matchInt32.of_int(Types.Fid.Set.cardinalt.fids)with|0l->fid1l(* 0l is pre-allocated for the FS root *)|n->ifn>=t.max_fidsthenNoneelseTypes.Fid.Set.max_eltt.fids|>Types.Fid.to_int32|>Int32.succ|>fid|>function|Some_asr->r|None->min_fidtletrecallocate_fidt=letopenLwt.Infixinmatchnext_fidtwith|None->ifdispatcher_is_runningtthen(Log.info(funf->f"FID pool exhausted (will wait for a free one; \
deadlock possible)");Lwt_condition.waitt.free_fids_c>>=fun()->allocate_fidt)elseLwt.return(Error(`Msg"connection disconnected"))|Somefid->t.fids<-Types.Fid.Set.addfidt.fids;Lwt.return(Okfid)letmark_fid_as_freetfid=t.fids<-Types.Fid.Set.removefidt.fids;Lwt_condition.signalt.free_fids_c()letdeallocate_fidtfid=letopenLwtinclunktfid>>=fun_->(* the spec says to assume the fid is clunked now *)mark_fid_as_freetfid;Lwt.return()endletwalk_from_roott=LowLevel.walktt.rootletwith_fidtf=letopenLwtinLowLevel.allocate_fidt>>*=funfid->finally(fun()->ffid)(fun()->LowLevel.deallocate_fidtfid)letwith_walk_from_roottpathf=letopenLwtinLowLevel.allocate_fidt>>*=funnewfid->LowLevel.walktt.rootnewfidpath>>=function|Errore->(* No need to clunk the fid as it's not bound *)LowLevel.mark_fid_as_freetnewfid;Lwt.return(Errore)|Ok_->(* I don't need to know the qids *)finally(fun()->fnewfid)(fun()->LowLevel.deallocate_fidtnewfid)letwritetpathoffsetbuf=letopenLowLevelinwith_walk_from_roottpath(funnewfid->openfidtnewfidTypes.OpenMode.write_only>>*=fun_->letrecloopoffsetremaining=letlen=Cstruct.lenremaininginiflen=0thenLwt.return(Ok())elsebeginletto_request=minlen(Int32.to_intt.maximum_payload)inwritetnewfidoffset(Cstruct.subremaining0to_request)>>*=fun{Response.Write.count}->letcount=Int32.to_intcountinletremaining=Cstruct.shiftremainingcountinloopInt64.(addoffset(of_intcount))remainingendinloopoffsetbuf)letreadtpathoffsetcount=letopenLowLevelinwith_walk_from_roottpath(funnewfid->openfidtnewfidTypes.OpenMode.read_only>>*=fun_->letrecloopaccoffsetremaining=letto_request=minremainingt.maximum_payloadinreadtnewfidoffsetto_request>>*=fun{Response.Read.data}->letn=Cstruct.lendatainifn=0thenLwt.return(Ok(List.revacc))elseloop(data::acc)Int64.(addoffset(of_intn))Int32.(subremaining(of_intn))inloop[]offsetcount)letcreatetpathnameperm=letopenLowLevelinwith_walk_from_roottpath(funnewfid->createtnewfidnamepermTypes.OpenMode.read_only>>*=fun_->Lwt.return(Ok()))letmkdirtpathnameperm=createtpathname{permwithTypes.FileMode.is_directory=true}letremovetpath=letopenLowLevelinletopenLwt.Infixinletfid=t.rootinLowLevel.allocate_fidt>>*=funnewfid->walktfidnewfidpath>>=function|Errore->(* We must clunk the fid ourselves *)clunktnewfid>>=fun_->(* ignore cascade error *)mark_fid_as_freetnewfid;Lwt.return(Errore)|Ok_->removetnewfid>>=funresult->(* Fid has been clunked by the remove call even on failure *)mark_fid_as_freetnewfid;Lwt.returnresultletstattpath=letopenLowLevelinwith_walk_from_roottpath(funnewfid->stattnewfid>>*=fun{Response.Stat.stat}->Lwt.return(Okstat))letafter_disconnectt=t.shutdown_complete_tletdisconnectt=letopenLwtinLwt_mutex.with_lockt.shutdown_m(fun()->ifdispatcher_is_runningtthenbegin(* Mark the connection as shutting down, so the dispatcher will quit *)t.please_shutdown<-true;(* Send a request, to unblock the dispatcher *)LowLevel.flushtTypes.Tag.notag>>=fun_->(* Wait for the dispatcher to shutdown *)t.shutdown_complete_t>>=fun()->(* Any new callers of `rpc` will fail immediately without blocking. *)return()endelsereturn())moduleKV_RO=structopenLwttype'aio='aLwt.ttypet=connectiontypeerror=Mirage_kv.errorletpp_error=Mirage_kv.pp_errortypepage_aligned_buffer=Cstruct.tletparse_pathx=String.cutsx~sep:"/"letreadtkeyoffsetlength=letpath=parse_pathkeyinletcount=Int64.to_int32lengthin(* FIXME: Error on overflow? *)readtpathoffsetcount>>=function|Okbufs->return(Okbufs)|_->return(Error(`Unknown_keykey))letsizetkey=letpath=parse_pathkeyinstattpath>>=function|Okstat->return(Okstat.Types.Stat.length)|_->return(Error(`Unknown_keykey))letmemtkey=letpath=parse_pathkeyinstattpath>>=function|Ok_->return(Oktrue)|_->return(Okfalse)letdisconnect=disconnectendletreaddirtpath=letopenLowLevelinwith_walk_from_roottpath(funnewfid->openfidtnewfidTypes.OpenMode.read_only>>*=fun_->letrecloopaccoffset=readtnewfidoffsett.maximum_payload>>*=fun{Response.Read.data}->ifCstruct.lendata=0thenLwt.return(Okacc)else(* Data should be an integral number of marshalled Stat.ts *)letmoduleStatArray=Types.Arr(Types.Stat)in(Lwt.return(StatArray.readdata))>>*=fun(stats,rest)->assert(Cstruct.lenrest=0);loop(acc@stats)Int64.(addoffset(of_int(Cstruct.lendata)))inloop[]0L)(* 8215 = 8192 + 23 (maximum overhead in a write packet) *)letconnectflow?(msize=8215l)?(username="nobody")?(max_fids=100l)?(aname="/")()=letreader=Reader.createflowinletwriter=flowinLowLevel.versionreaderwritermsizeTypes.Version.unix>>*=funversion->letmsize=minmsizeversion.Response.Version.msizeinletupgraded=version.Response.Version.version=Types.Version.unixin(* Compute the maximum payload size *)letsmallest_read_response={Response.tag=Types.Tag.notag;payload=Response.Read{Response.Read.data=Cstruct.create0}}inletmaximum_read_payload=Int32.(submsize(of_int(Response.sizeofsmallest_read_response)))inletsmallest_write_request={Request.tag=Types.Tag.notag;payload=Request.Write{Request.Write.data=Cstruct.create0;fid=Types.Fid.nofid;offset=0L}}inletmaximum_write_payload=Int32.(submsize(of_int(Request.sizeofsmallest_write_request)))indebug(funf->f"Negotiated maximum message size: %ld bytes"msize);debug(funf->f"Maximum read payload would be: %ld bytes"maximum_read_payload);debug(funf->f"Maximum write payload would be: %ld bytes"maximum_write_payload);(* For compatibility, use the smallest of the two possible maximums *)letmaximum_payload=minmaximum_read_payloadmaximum_write_payloadindebug(funf->f"We will use a global maximum payload of: %ld bytes"maximum_payload);(* We use the convention that fid 0l is the root fid. We'll never clunk
this one so we can always re-explore the filesystem from the root. *)letroot=matchTypes.Fid.of_int320lwithOkx->x|_->assertfalseinletshutdown_complete_t,shutdown_complete_wakener=Lwt.task()inlett={reader;writer;root;msize;upgraded;maximum_payload;transmit_m=Lwt_mutex.create();please_shutdown=false;shutdown_m=Lwt_mutex.create();shutdown_complete_t;wakeners=Types.Tag.Map.empty;free_tags=Types.Tag.recommended;free_tags_c=Lwt_condition.create();max_fids;fids=Types.Fid.Set.empty;free_fids_c=Lwt_condition.create();}inLowLevel.attachreaderwriterrootTypes.Fid.nofidusernameanameNone>>*=function{Response.Attach.qid}->debug(funf->f"Successfully received a root qid: %s"(Sexplib.Sexp.to_string_hum(Types.Qid.sexp_of_tqid)));Lwt.async(fun()->letopenLwt.InfixinLwt.catch(fun()->dispatcher_tshutdown_complete_wakenert>>=function|Result.Error(`Msgm)->err(funf->f"dispatcher caught %s: no more responses will be handled"m);Lwt.return()|Result.Ok()->Lwt.return())(fune->err(funf->f"dispatcher caught %s: no more responses will be handled"(Printexc.to_stringe));Lwt.return())>>=fun()->Lwt.wakeupshutdown_complete_wakener();(* Wake up any existing `rpc` threads blocked on a free fid *)Lwt_condition.broadcastt.free_fids_c();(* Notify any remaining blocked threads that we're down *)Types.Tag.Map.iter(funtagwakener->info(funf->f"Sending disconnection to request with tag %d"(Types.Tag.to_inttag));Lwt.wakeup_laterwakener(Error(`Msg"connection disconnected"));t.wakeners<-Types.Tag.Map.removetagt.wakeners(* Note existing `rpc` threads blocked waiting for a free tag will
wake up one by one *))t.wakeners;Lwt.return());Lwt.return(Okt)end