123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405(**************************************************************************)(* *)(* SPDX-License-Identifier LGPL-2.1 *)(* Copyright (C) *)(* CEA (Commissariat à l'énergie atomique et aux énergies alternatives) *)(* *)(**************************************************************************)(* -------------------------------------------------------------------------- *)(* --- Socket Server Options --- *)(* -------------------------------------------------------------------------- *)moduleSenv=Server_parametersletdkey=Senv.dkey_protocolletsocket_group=Senv.add_group"Sockets"let()=Parameter_customize.is_not_reconfigurable()let()=Parameter_customize.set_groupsocket_groupmoduleSocket=Senv.String(structletoption_name="-server-socket"letarg_name="url"letdefault=""lethelp="Launch the Socket server (in background).\n\
The server can handle GET requests during the\n\
execution of the frama-c command line.\n\
Finally, the server is executed until shutdown.\n\
For the Internet socket domain, the default url is '127.0.0.1'."end)let()=Parameter_customize.set_groupsocket_groupmoduleSocketSize=Senv.Int(structletoption_name="-server-socket-size"letarg_name="n"letdefault=256lethelp="Control the size of socket buffers (in ko, default 256)."end)let()=Parameter_customize.set_groupsocket_groupmoduleSocketDomain=Senv.String(structletoption_name="-server-socket-domain"letarg_name="unix|internet"letdefault="unix"lethelp="Socket domain to be used: 'unix' or 'internet'.\n\
'unix' is faster, but does not work on Windows\n\
nor between different machines."end)let()=SocketDomain.set_possible_values["unix";"internet"]let()=Parameter_customize.set_groupsocket_groupmoduleSocketPort=Senv.Int(structletoption_name="-server-socket-port"letarg_name="n"letdefault=9225lethelp="Socket port to be used, if the domain\n\
(-server-socket-domain) is 'internet'.\n\
The default port is 9225 (9-22-5, I-V-E)."end)let()=SocketPort.set_range~min:1~max:65535let_=Server_doc.protocol~title:"Socket Protocol"~readme:"server_socket.md"(* -------------------------------------------------------------------------- *)(* --- Socket Bytes Read & Write --- *)(* -------------------------------------------------------------------------- *)typechannel={sock:Unix.file_descr;(* Socket *)snd:bytes;(* SND bytes buffer, re-used for transport *)rcv:bytes;(* RCV bytes buffer, re-used for transport *)brcv:Buffer.t;(* RCV data buffer, accumulated *)bsnd:Buffer.t;(* SND data buffer, accumulated *)}letread_bytes{sock;rcv;brcv}=(* rcv buffer is only used locally *)lets=Bytes.lengthrcvinletrecscanp=(* try to fill RCV buffer *)letn=tryUnix.readsockrcvp(s-p)withUnix.Unix_error((EAGAIN|EWOULDBLOCK),_,_)->0inletp=p+ninifn>0&&p<sthenscanpelsepinletn=scan0inifn>0thenBuffer.add_subbytesbrcvrcv0nletsend_bytes{sock;snd;bsnd}=(* snd buffer is only used locally *)letn=Buffer.lengthbsndinifn>0thenlets=Bytes.lengthsndinletrecsendp=(* try to flush BSND buffer *)letw=min(n-p)sinBuffer.blitbsndpsnd0w;letr=tryUnix.single_writesocksnd0wwithUnix.Unix_error((EAGAIN|EWOULDBLOCK),_,_)->0inletp=p+rinifr>0&&p<nthensendpelsepinletp=send0inifp>0thenlettail=Buffer.subbsndp(n-p)inBuffer.resetbsnd;Buffer.add_stringbsndtail(* -------------------------------------------------------------------------- *)(* --- Data Chunks Encoding --- *)(* -------------------------------------------------------------------------- *)letread_datach=try(* Try to read all the data.
In case there is not enough bytes in the buffer,
calls to Buffer.sub would raise Invalid_argument. *)leth=matchBuffer.nthch.brcv0with|'S'->3|'L'->7|'W'->15|_->raise(Invalid_argument"Server_socket.read")inlethex=Buffer.subch.brcv1hinletlen=int_of_string("0x"^hex)inletdata=Buffer.subch.brcv(1+h)leninletp=1+h+leninletn=Buffer.lengthch.brcv-pin(* TODO[LC]: inefficient move. Requires a ring-buffer. *)letrest=Buffer.subch.brcvpninBuffer.resetch.brcv;Buffer.add_stringch.brcvrest;SomedatawithInvalid_argument_->Noneletwrite_datachdata=beginletlen=String.lengthdatainlethex=iflen<0xFFFthenPrintf.sprintf"S%03x"lenelseiflen<0xFFFFFFFthenPrintf.sprintf"L%07x"lenelsePrintf.sprintf"W%015x"leninBuffer.add_stringch.bsndhex;Buffer.add_stringch.bsnddata;end(* -------------------------------------------------------------------------- *)(* --- Request Encoding --- *)(* -------------------------------------------------------------------------- *)letjfieldfdjs=Json.fieldfdjs|>Json.stringletdecode(data:string):stringMain.request=matchdatawith|"\"POLL\""->`Poll|"\"SHUTDOWN\""->`Shutdown|_->letjs=Yojson.Basic.from_stringdatainmatchjfield"cmd"jswith|"GET"|"SET"|"EXEC"->letid=jfield"id"jsinletrequest=jfield"request"jsinletdata=Json.field"data"jsin`Request(id,request,data)|"SIGON"->`SigOn(jfield"id"js)|"SIGOFF"->`SigOff(jfield"id"js)|"KILL"->`Kill(jfield"id"js)|_->Senv.feedback~dkey"Invalid socket command:@ @[<hov 2>%a@]"Json.ppjs;raiseNot_foundletencode(resp:stringMain.response):string=letjs=matchrespwith|`Data(id,data)->`Assoc["res",`String"DATA";"id",`Stringid;"data",data]|`Error(id,msg)->`Assoc["res",`String"ERROR";"id",`Stringid;"msg",`Stringmsg]|`Killedid->`Assoc["res",`String"KILLED";"id",`Stringid]|`Rejectedid->`Assoc["res",`String"REJECTED";"id",`Stringid]|`Signalid->`Assoc["res",`String"SIGNAL";"id",`Stringid]|`CmdLineOn->`String"CMDLINEON"|`CmdLineOff->`String"CMDLINEOFF"inYojson.Basic.to_string~std:falsejsletparsech=letrecscancmdsch=matchread_datachwith|None->List.revcmds|Somedata->matchdecodedatawith|cmd->scan(cmd::cmds)ch|exception_->scancmdschinscan[]ch(* -------------------------------------------------------------------------- *)(* --- Socket Messages --- *)(* -------------------------------------------------------------------------- *)letcallbackchrs=List.iter(funr->matchencoderwith|data->write_datachdata|exceptionerr->Senv.feedback~dkey"Socket: encoding error %S@."(Printexc.to_stringerr))rs;send_byteschletcommandsch=beginread_bytesch;matchparsechwith|[]->send_bytesch;None|requests->SomeMain.{requests;callback=callbackch}end(* -------------------------------------------------------------------------- *)(* --- Establish the Server --- *)(* -------------------------------------------------------------------------- *)typesocket={socket:Unix.file_descr;mutablechannel:channeloption;}letclose(s:socket)=matchs.channelwithNone->()|Somech->s.channel<-None;Unix.closech.sockletset_socket_sizesockopts=beginletnbytes=s*1024in(tryUnix.setsockopt_intsockoptnbyteswithUnix.Unix_error(err,_,_)->letmsg=Unix.error_messageerrinSenv.warning~once:true"Invalid socket size (%d: %s)"nbytesmsg);Unix.getsockopt_intsockoptendletchannel(s:socket)=matchs.channelwith|Some_aschan->chan|None->tryletsock,_=Unix.accept~cloexec:trues.socketinUnix.set_nonblocksock;letsize=SocketSize.get()inletrcv=set_socket_sizesockSO_RCVBUFsizeinletsnd=set_socket_sizesockSO_SNDBUFsizeinSenv.feedback~dkey~level:2"Socket size in:%d out:%d@."rcvsnd;Senv.debug"Client connected";letch=Some{sock;snd=Bytes.createsnd;rcv=Bytes.creatercv;bsnd=Buffer.createsnd;brcv=Buffer.creatercv;}ins.channel<-ch;chwithUnix.Unix_error(EAGAIN,_,_)->Noneletfetch(s:socket)()=trymatchchannelswith|None->None|Somech->commandschwith|Unix.Unix_error(EPIPE,_,_)->Senv.debug"Client disconnected";closes;None|exn->Senv.warning"Socket: exn %s"(Printexc.to_stringexn);closes;Noneletestablish_serverfd=letsocket={socket=fd;channel=None}intryUnix.listenfd1;Senv.feedback~dkey"Socket server: listening up to 1 pending request";begintryignore(Sys.signalSys.sigpipeSignal_ignore);with_->Senv.debug"SIGPIPE unsupported in this OS, ignoring"end;letpretty=Format.pp_print_stringinletserver=Main.create~pretty~fetch:(fetchsocket)()inExtlib.safe_at_exitbeginfun()->Main.stopserver;closesocket;end;Main.startserver;Cmdline.at_normal_exitbeginfun()->Main.runserver;closesocket;end;withexn->closesocket;raiseexn(* -------------------------------------------------------------------------- *)(* --- Synchronous Server --- *)(* -------------------------------------------------------------------------- *)(* address of the currently-running server, if any *)letserver_addr=refNone(* port of the currently-running server, if any (Internet domain only) *)letserver_port=refNoneletcmdline()=matchSocket.get()with|""->()|addr->match(SocketDomain.get())with|"unix"->beginmatch!server_addrwith|Someaddr0->beginifaddr0<>addrthenSenv.warning"Socket server already running on [%s]."addr0end|None->tryserver_addr:=Someaddr;letaddr_path=Filepath.of_stringaddrinifFilesystem.existsaddr_paththenUnix.unlinkaddr;letfd=Unix.socketPF_UNIXSOCK_STREAM0inUnix.bindfd(ADDR_UNIXaddr);ifSenv.is_debug_key_enableddkeythenSenv.feedback~dkey"Socket server running on [%s]."addrelseSenv.feedback"Socket server running.";establish_serverfd;withexn->Senv.fatal"Server Unix socket failed.@\nError: %s"(Printexc.to_stringexn)end|"internet"->beginletaddr=ifaddr=""then"127.0.0.1"elseaddrinletport=SocketPort.get()inmatch!server_addr,!server_portwith|Someaddr0,Someport0->beginifaddr0<>addr||port0<>portthenSenv.warning"Socket server already running on [%s:%d]."addr0port0end|_->tryserver_addr:=Someaddr;server_port:=Someport;letfd=Unix.socketPF_INETSOCK_STREAM0inletaddr_inet=Unix.inet_addr_of_stringaddrinSenv.feedback~dkey"Socket server will bind on %s:%d..."addrport;Unix.bindfd(Unix.ADDR_INET(addr_inet,port));ifSenv.is_debug_key_enableddkeythenSenv.feedback~dkey"Socket server running on [%s:%d]."addrportelseSenv.feedback"Socket server running.";establish_serverfd;withexn->matchexnwith|Unix.Unix_error(Unix.EADDRINUSE,"bind",_)->(* Note: this does not happen when SO_REUSEPORT is used *)Senv.abort"bind failed: EADDRINUSE. Terminate all previous \
Frama-C server processes and restart Ivette."|_->Senv.fatal"Server Internet socket failed.@\nError: %s"(Printexc.to_stringexn)end|_->assertfalselet()=Boot.Main.extendcmdline(* -------------------------------------------------------------------------- *)