client_context_unix.ml
(*****************************************************************************)
(* *)
(* Open Source License *)
(* Copyright (c) 2018 Dynamic Ledger Solutions, Inc. <contact@tezos.com> *)
(* Copyright (c) 2018 Nomadic Labs, <contact@nomadic-labs.com> *)
(* *)
(* Permission is hereby granted, free of charge, to any person obtaining a *)
(* copy of this software and associated documentation files (the "Software"),*)
(* to deal in the Software without restriction, including without limitation *)
(* the rights to use, copy, modify, merge, publish, distribute, sublicense, *)
(* and/or sell copies of the Software, and to permit persons to whom the *)
(* Software is furnished to do so, subject to the following conditions: *)
(* *)
(* The above copyright notice and this permission notice shall be included *)
(* in all copies or substantial portions of the Software. *)
(* *)
(* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*)
(* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *)
(* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *)
(* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*)
(* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *)
(* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *)
(* DEALINGS IN THE SOFTWARE. *)
(* *)
(*****************************************************************************)
class unix_wallet ~base_dir ~password_filename : Client_context.wallet =
object (self)
val lock_mutex = Lwt_mutex.create ()
method load_passwords =
match password_filename with
| None -> None
| Some filename ->
if Sys.file_exists filename then Some (Lwt_io.lines_of_file filename)
else None
method read_file path =
let open Lwt_syntax in
Lwt.catch
(fun () ->
let* content = Lwt_io.(with_file ~mode:Input path read) in
return_ok content)
(fun exn -> failwith "cannot read file (%s)" (Printexc.to_string exn))
method private filename alias_name =
Filename.concat
base_dir
(String.map (function ' ' -> '_' | c -> c) alias_name ^ "s")
method get_base_dir = base_dir
method with_lock : type a. (unit -> a Lwt.t) -> a Lwt.t =
fun f ->
Lwt_mutex.with_lock lock_mutex (fun () ->
let open Lwt_syntax in
let unlock fd =
let fd = Lwt_unix.unix_file_descr fd in
Unix.lockf fd Unix.F_ULOCK 0 ;
Unix.close fd
in
let lock () =
let* fd =
Lwt_unix.openfile
(Filename.concat base_dir "wallet_lock")
Lwt_unix.[O_CREAT; O_WRONLY]
0o644
in
let* () = Lwt_unix.lockf fd Unix.F_LOCK 0 in
let sighandler =
Lwt_unix.on_signal Sys.sigint (fun _s -> unlock fd)
in
Lwt.return (fd, sighandler)
in
let* fd, sh = lock () in
(* catch might be useless if f always uses the error monad *)
let* res =
Lwt.finalize f (fun () ->
unlock fd ;
Lwt.return_unit)
in
Lwt_unix.disable_signal_handler sh ;
Lwt.return res)
method load : type a.
string -> default:a -> a Data_encoding.encoding -> a tzresult Lwt.t =
fun alias_name ~default encoding ->
let open Lwt_result_syntax in
let filename = self#filename alias_name in
if not (Sys.file_exists filename) then return default
else
let* json =
trace_eval
(fun () ->
error_of_fmt "could not read the %s alias file" alias_name)
(Lwt_utils_unix.Json.read_file filename)
in
match Data_encoding.Json.destruct encoding json with
| exception e ->
failwith
"did not understand the %s alias file %s : %s"
alias_name
filename
(Printexc.to_string e)
| data -> return data
method write : type a.
string -> a -> a Data_encoding.encoding -> unit tzresult Lwt.t =
fun alias_name list encoding ->
let open Lwt_result_syntax in
trace_eval (fun () ->
error_of_fmt "could not write the %s alias file." alias_name)
@@ Error_monad.catch_es (fun () ->
let*! () = Lwt_utils_unix.create_dir base_dir in
let filename = self#filename alias_name in
let json = Data_encoding.Json.construct encoding list in
let content = Data_encoding.Json.to_string ~minify:false json in
let*! res =
Lwt_utils_unix.with_atomic_open_out ~overwrite:true filename
@@ fun chan -> Lwt_utils_unix.write_string chan content
in
match res with
| Ok x -> return x
| Error {unix_code; caller; arg; _} ->
tzfail (Exn (Unix.Unix_error (unix_code, caller, arg))))
method last_modification_time : string -> float option tzresult Lwt.t =
let open Lwt_result_syntax in
fun alias_name ->
let filename = self#filename alias_name in
let*! exists = Lwt_unix.file_exists filename in
if exists then
let* stat = Error_monad.catch_s (fun () -> Lwt_unix.stat filename) in
return_some stat.st_mtime
else return_none
end
class unix_prompter : Client_context.prompter =
object
method prompt : type a. (a, string tzresult) Client_context.lwt_format -> a
=
Format.kasprintf (fun msg ->
print_string msg ;
let line = read_line () in
Lwt.return_ok line)
method prompt_password : type a.
(a, Bytes.t tzresult) Client_context.lwt_format -> a =
Format.kasprintf (fun msg ->
print_string msg ;
let line =
if Unix.isatty Unix.stdin then Lwt_utils_unix.getpass ()
else read_line ()
in
Lwt.return_ok (Bytes.of_string line))
method multiple_password_retries = true
end
class unix_logger ~base_dir : Client_context.printer =
let startup = Format.asprintf "%a" Time.System.pp_hum (Time.System.now ()) in
let log channel msg =
let open Lwt_syntax in
match channel with
| "stdout" ->
print_endline msg ;
Lwt.return_unit
| "stderr" ->
prerr_endline msg ;
Lwt.return_unit
| log ->
let open Filename.Infix in
let* () = Lwt_utils_unix.create_dir (base_dir // "logs" // log) in
Lwt_io.with_file
~flags:Unix.[O_APPEND; O_CREAT; O_WRONLY]
~mode:Lwt_io.Output
(base_dir // "logs" // log // startup)
(fun chan -> Lwt_io.write chan msg)
in
object
inherit Client_context.simple_printer log
end
class unix_io_wallet ~base_dir ~password_filename : Client_context.io_wallet =
object
inherit unix_wallet ~base_dir ~password_filename
inherit unix_logger ~base_dir
inherit unix_prompter
end
class unix_ui : Client_context.ui =
object
method sleep f = Lwt_unix.sleep f
method exit : 'a. int -> 'a = fun i -> Lwt_exit.exit_and_raise i
method now = Tezos_base.Time.System.now
end
class unix_full ~base_dir ~chain ~block ~confirmations ~password_filename
~rpc_config ~verbose_rpc_error_diagnostics : Client_context.full =
object
inherit unix_logger ~base_dir
inherit unix_prompter
inherit unix_wallet ~base_dir ~password_filename
inherit
Tezos_rpc_http_client_unix.RPC_client_unix.http_ctxt
rpc_config
(Media_type.Command_line.of_command_line rpc_config.media_type)
inherit unix_ui
method chain = chain
method block = block
method confirmations = confirmations
method verbose_rpc_error_diagnostics = verbose_rpc_error_diagnostics
end
class unix_mockup ~base_dir ~mem_only ~mockup_env ~chain_id ~rpc_context
~protocol_data : Client_context.full =
object
inherit unix_logger ~base_dir
inherit unix_prompter
inherit unix_wallet ~base_dir ~password_filename:None
inherit
Tezos_mockup.RPC_client.mockup_ctxt
base_dir
mem_only
mockup_env
chain_id
rpc_context
protocol_data
inherit unix_ui
method chain = `Hash chain_id
method block = `Head 0
method confirmations = None
method verbose_rpc_error_diagnostics = true
end
class unix_proxy ~base_dir ?protocol ~chain ~block ~confirmations
~password_filename ~rpc_config ~mode () : Client_context.full =
object
inherit unix_logger ~base_dir
inherit unix_prompter
inherit unix_wallet ~base_dir ~password_filename
inherit
Tezos_proxy_rpc.RPC_client.http_local_ctxt
(new unix_logger ~base_dir)
(new Tezos_rpc_http_client_unix.RPC_client_unix.http_ctxt
rpc_config
(Media_type.Command_line.of_command_line rpc_config.media_type))
mode
protocol
inherit unix_ui
method chain = chain
method block = block
method confirmations = confirmations
method verbose_rpc_error_diagnostics = true
end