2017-05-26 14:30:34 +00:00
|
|
|
(* (c) 2017 Hannes Mehnert, all rights reserved *)
|
|
|
|
|
|
|
|
(* the process responsible for buffering console IO *)
|
|
|
|
|
2018-09-19 19:16:44 +00:00
|
|
|
(* communication channel is a single unix domain socket. The following commands
|
|
|
|
can be issued:
|
|
|
|
- Add name (by vmmd) --> creates a new console slurper for name,
|
|
|
|
and starts a read_console task
|
|
|
|
- Attach name --> attaches console of name: send existing stuff to client,
|
|
|
|
and record the requesting socket to receive further messages. A potential
|
|
|
|
earlier subscriber to the same console is closed. *)
|
2017-05-26 14:30:34 +00:00
|
|
|
|
|
|
|
open Lwt.Infix
|
|
|
|
|
|
|
|
open Astring
|
|
|
|
|
2018-09-09 18:52:04 +00:00
|
|
|
let my_version = `WV2
|
2017-05-26 14:30:34 +00:00
|
|
|
|
|
|
|
let pp_unix_error ppf e = Fmt.string ppf (Unix.error_message e)
|
|
|
|
|
2018-05-02 17:52:18 +00:00
|
|
|
let active = ref String.Map.empty
|
2017-05-26 14:30:34 +00:00
|
|
|
|
2018-05-02 17:52:18 +00:00
|
|
|
let read_console name ring channel () =
|
2018-09-09 18:52:04 +00:00
|
|
|
let id = Vmm_core.id_of_string name in
|
2017-05-26 14:30:34 +00:00
|
|
|
Lwt.catch (fun () ->
|
|
|
|
let rec loop () =
|
|
|
|
Lwt_io.read_line channel >>= fun line ->
|
|
|
|
Logs.debug (fun m -> m "read %s" line) ;
|
|
|
|
let t = Ptime_clock.now () in
|
|
|
|
Vmm_ring.write ring (t, line) ;
|
2018-05-02 17:52:18 +00:00
|
|
|
(match String.Map.find name !active with
|
|
|
|
| None -> Lwt.return_unit
|
|
|
|
| Some fd ->
|
2018-09-09 18:52:04 +00:00
|
|
|
Vmm_lwt.write_wire fd (Vmm_wire.Console.data my_version id t line) >>= function
|
|
|
|
| Error _ ->
|
|
|
|
Lwt.catch (fun () -> Lwt_unix.close fd) (fun _ -> Lwt.return_unit) >|= fun () ->
|
|
|
|
active := String.Map.remove name !active
|
2018-05-02 17:52:18 +00:00
|
|
|
| Ok () -> Lwt.return_unit) >>=
|
|
|
|
loop
|
2017-05-26 14:30:34 +00:00
|
|
|
in
|
|
|
|
loop ())
|
|
|
|
(fun e ->
|
|
|
|
begin match e with
|
|
|
|
| Unix.Unix_error (e, f, _) ->
|
|
|
|
Logs.err (fun m -> m "%s error in %s: %a" name f pp_unix_error e)
|
|
|
|
| End_of_file ->
|
|
|
|
Logs.debug (fun m -> m "%s end of file while reading" name)
|
|
|
|
| exn ->
|
|
|
|
Logs.err (fun m -> m "%s error while reading %s" name (Printexc.to_string exn))
|
|
|
|
end ;
|
|
|
|
Lwt_io.close channel)
|
|
|
|
|
|
|
|
let open_fifo name =
|
2018-04-25 11:15:53 +00:00
|
|
|
let fifo = Fpath.(Vmm_core.tmpdir / name + "fifo") in
|
2017-05-26 14:30:34 +00:00
|
|
|
Lwt.catch (fun () ->
|
|
|
|
Logs.debug (fun m -> m "opening %a for reading" Fpath.pp fifo) ;
|
|
|
|
Lwt_io.open_file ~mode:Lwt_io.Input (Fpath.to_string fifo) >>= fun channel ->
|
|
|
|
Lwt.return (Some channel))
|
|
|
|
(function
|
|
|
|
| Unix.Unix_error (e, f, _) ->
|
|
|
|
Logs.err (fun m -> m "%a error in %s: %a" Fpath.pp fifo f pp_unix_error e) ;
|
|
|
|
Lwt.return None
|
|
|
|
| exn ->
|
|
|
|
Logs.err (fun m -> m "%a error while reading %s" Fpath.pp fifo (Printexc.to_string exn)) ;
|
|
|
|
Lwt.return None)
|
|
|
|
|
|
|
|
let t = ref String.Map.empty
|
|
|
|
|
2018-09-09 18:52:04 +00:00
|
|
|
let add_fifo id =
|
|
|
|
let name = Vmm_core.string_of_id id in
|
2017-05-26 14:30:34 +00:00
|
|
|
open_fifo name >|= function
|
|
|
|
| Some f ->
|
|
|
|
let ring = Vmm_ring.create () in
|
|
|
|
Logs.debug (fun m -> m "inserting %s" name) ;
|
|
|
|
let map = String.Map.add name ring !t in
|
|
|
|
t := map ;
|
2018-05-02 17:52:18 +00:00
|
|
|
Lwt.async (read_console name ring f) ;
|
2017-05-26 14:30:34 +00:00
|
|
|
Ok "reading"
|
|
|
|
| None ->
|
|
|
|
Error (`Msg "opening")
|
|
|
|
|
2018-09-09 18:52:04 +00:00
|
|
|
let attach s id =
|
|
|
|
let name = Vmm_core.string_of_id id in
|
|
|
|
Logs.debug (fun m -> m "attempting to attach %a" Vmm_core.pp_id id) ;
|
2017-05-26 14:30:34 +00:00
|
|
|
match String.Map.find name !t with
|
|
|
|
| None -> Lwt.return (Error (`Msg "not found"))
|
|
|
|
| Some r ->
|
2018-09-19 19:16:44 +00:00
|
|
|
let entries = Vmm_ring.read r in
|
2017-05-26 14:30:34 +00:00
|
|
|
Logs.debug (fun m -> m "found %d history" (List.length entries)) ;
|
|
|
|
Lwt_list.iter_s (fun (i, v) ->
|
2018-09-19 19:16:44 +00:00
|
|
|
let msg = Vmm_wire.Console.data my_version id i v in
|
|
|
|
Vmm_lwt.write_wire s msg >|= fun _ -> ())
|
|
|
|
entries >>= fun () ->
|
|
|
|
(match String.Map.find name !active with
|
|
|
|
| None -> Lwt.return_unit
|
|
|
|
| Some s -> Vmm_lwt.safe_close s) >|= fun () ->
|
|
|
|
active := String.Map.add name s !active ;
|
|
|
|
Ok "attached"
|
2017-05-26 14:30:34 +00:00
|
|
|
|
|
|
|
let handle s addr () =
|
2018-09-09 18:52:04 +00:00
|
|
|
Logs.info (fun m -> m "handling connection %a" Vmm_lwt.pp_sockaddr addr) ;
|
2017-05-26 14:30:34 +00:00
|
|
|
let rec loop () =
|
2018-09-09 18:52:04 +00:00
|
|
|
Vmm_lwt.read_wire s >>= function
|
2018-03-18 17:30:43 +00:00
|
|
|
| Error (`Msg msg) ->
|
|
|
|
Logs.err (fun m -> m "error while reading %s" msg) ;
|
|
|
|
loop ()
|
|
|
|
| Error _ ->
|
|
|
|
Logs.err (fun m -> m "exception while reading") ;
|
|
|
|
Lwt.return_unit
|
2018-09-09 18:52:04 +00:00
|
|
|
| Ok (hdr, _) when Vmm_wire.is_reply hdr ->
|
|
|
|
Logs.err (fun m -> m "unexpected reply") ;
|
|
|
|
loop ()
|
2017-05-26 14:30:34 +00:00
|
|
|
| Ok (hdr, data) ->
|
2018-09-19 19:16:44 +00:00
|
|
|
(if not (Vmm_wire.version_eq hdr.Vmm_wire.version my_version) then
|
2017-05-26 14:30:34 +00:00
|
|
|
Lwt.return (Error (`Msg "ignoring data with bad version"))
|
|
|
|
else
|
2018-09-09 18:52:04 +00:00
|
|
|
match Vmm_wire.decode_strings data with
|
2018-03-22 12:36:50 +00:00
|
|
|
| Error e -> Lwt.return (Error e)
|
2018-09-19 19:16:44 +00:00
|
|
|
| Ok (id, _) -> match Vmm_wire.Console.int_to_op hdr.Vmm_wire.tag with
|
2018-09-09 18:52:04 +00:00
|
|
|
| Some Vmm_wire.Console.Add_console -> add_fifo id
|
|
|
|
| Some Vmm_wire.Console.Attach_console -> attach s id
|
|
|
|
| Some Vmm_wire.Console.Data -> Lwt.return (Error (`Msg "unexpected Data"))
|
2018-09-19 19:16:44 +00:00
|
|
|
| None -> Lwt.return (Error (`Msg "unknown command"))) >>= (function
|
2018-09-09 18:52:04 +00:00
|
|
|
| Ok msg -> Vmm_lwt.write_wire s (Vmm_wire.success ~msg my_version hdr.Vmm_wire.id hdr.Vmm_wire.tag)
|
2017-05-26 14:30:34 +00:00
|
|
|
| Error (`Msg msg) ->
|
|
|
|
Logs.err (fun m -> m "error while processing command: %s" msg) ;
|
2018-09-09 18:52:04 +00:00
|
|
|
Vmm_lwt.write_wire s (Vmm_wire.fail ~msg my_version hdr.Vmm_wire.id)) >>= function
|
2018-03-22 12:36:50 +00:00
|
|
|
| Ok () -> loop ()
|
|
|
|
| Error _ ->
|
|
|
|
Logs.err (fun m -> m "exception while writing to socket") ;
|
|
|
|
Lwt.return_unit
|
2017-05-26 14:30:34 +00:00
|
|
|
in
|
2018-03-18 17:30:43 +00:00
|
|
|
loop () >>= fun () ->
|
2018-04-01 21:13:11 +00:00
|
|
|
Lwt.catch (fun () -> Lwt_unix.close s) (fun _ -> Lwt.return_unit) >|= fun () ->
|
|
|
|
Logs.warn (fun m -> m "disconnected")
|
2017-05-26 14:30:34 +00:00
|
|
|
|
|
|
|
let jump _ file =
|
|
|
|
Sys.(set_signal sigpipe Signal_ignore) ;
|
|
|
|
Lwt_main.run
|
2018-04-25 11:15:53 +00:00
|
|
|
((Lwt_unix.file_exists file >>= function
|
|
|
|
| true -> Lwt_unix.unlink file
|
|
|
|
| false -> Lwt.return_unit) >>= fun () ->
|
|
|
|
let s = Lwt_unix.(socket PF_UNIX SOCK_STREAM 0) in
|
2018-04-03 20:58:31 +00:00
|
|
|
Lwt_unix.(bind s (ADDR_UNIX file)) >>= fun () ->
|
2017-05-26 14:30:34 +00:00
|
|
|
Lwt_unix.listen s 1 ;
|
|
|
|
let rec loop () =
|
|
|
|
Lwt_unix.accept s >>= fun (cs, addr) ->
|
|
|
|
Lwt.async (handle cs addr) ;
|
|
|
|
loop ()
|
|
|
|
in
|
|
|
|
loop ())
|
|
|
|
|
|
|
|
let setup_log style_renderer level =
|
|
|
|
Fmt_tty.setup_std_outputs ?style_renderer ();
|
|
|
|
Logs.set_level level;
|
|
|
|
Logs.set_reporter (Logs_fmt.reporter ~dst:Format.std_formatter ())
|
|
|
|
|
|
|
|
open Cmdliner
|
|
|
|
|
|
|
|
let setup_log =
|
|
|
|
Term.(const setup_log
|
|
|
|
$ Fmt_cli.style_renderer ()
|
|
|
|
$ Logs_cli.level ())
|
|
|
|
|
|
|
|
let socket =
|
2018-04-25 11:15:53 +00:00
|
|
|
let doc = "Socket to listen on" in
|
2018-09-19 19:16:44 +00:00
|
|
|
let sock = Vmm_core.socket_path `Console in
|
2018-04-25 11:15:53 +00:00
|
|
|
Arg.(value & opt string sock & info [ "s" ; "socket" ] ~doc)
|
2017-05-26 14:30:34 +00:00
|
|
|
|
|
|
|
let cmd =
|
|
|
|
Term.(ret (const jump $ setup_log $ socket)),
|
|
|
|
Term.info "vmm_console" ~version:"%%VERSION_NUM%%"
|
|
|
|
|
|
|
|
let () = match Term.eval cmd with `Ok () -> exit 0 | _ -> exit 1
|