diff --git a/src/clua-i-virtual-machine.cpp b/src/clua-i-virtual-machine.cpp index f1653eda8..7661f61ff 100644 --- a/src/clua-i-virtual-machine.cpp +++ b/src/clua-i-virtual-machine.cpp @@ -401,19 +401,6 @@ static int machine_obj_index_replace_memory_range(lua_State *L) { return 0; } -/// \brief This is the machine:destroy() method implementation. -/// \param L Lua state. -static int machine_obj_index_destroy(lua_State *L) { - lua_settop(L, 2); - auto &m = clua_check>(L, 1); - const bool keep_machine = lua_toboolean(L, 2); - if (cm_destroy(m.get(), keep_machine) != 0) { - return luaL_error(L, "%s", cm_get_last_error_message()); - } - m.release(); - return 0; -} - /// \brief This is the machine:snapshot() method implementation. /// \param L Lua state. static int machine_obj_index_snapshot(lua_State *L) { @@ -530,7 +517,6 @@ static const auto machine_obj_index = cartesi::clua_make_luaL_Reg_array({ {"write_virtual_memory", machine_obj_index_write_virtual_memory}, {"translate_virtual_address", machine_obj_index_translate_virtual_address}, {"replace_memory_range", machine_obj_index_replace_memory_range}, - {"destroy", machine_obj_index_destroy}, {"snapshot", machine_obj_index_snapshot}, {"commit", machine_obj_index_commit}, {"rollback", machine_obj_index_rollback}, @@ -544,27 +530,10 @@ static const auto machine_obj_index = cartesi::clua_make_luaL_Reg_array({ {"log_send_cmio_response", machine_obj_index_log_send_cmio_response}, }); -/// \brief This is the machine __close metamethod implementation. -/// \param L Lua state. -static int machine_obj_close(lua_State *L) { - auto &m = clua_check>(L, 1); - if (cm_destroy(m.get(), false) != 0) { - return luaL_error(L, "%s", cm_get_last_error_message()); - } - m.release(); - clua_close>(L); - return 0; -} - int clua_i_virtual_machine_init(lua_State *L, int ctxidx) { if (!clua_typeexists>(L, ctxidx)) { clua_createtype>(L, "cartesi machine object", ctxidx); clua_setmethods>(L, machine_obj_index.data(), 0, ctxidx); - // Override __close to actually destroy the machine - static const auto machine_class_meta = cartesi::clua_make_luaL_Reg_array({ - {"__close", machine_obj_close}, - }); - clua_setmetamethods>(L, machine_class_meta.data(), 0, ctxidx); } return 1; } diff --git a/src/clua-jsonrpc-machine.cpp b/src/clua-jsonrpc-machine.cpp index 55eb5455b..0ac586794 100644 --- a/src/clua-jsonrpc-machine.cpp +++ b/src/clua-jsonrpc-machine.cpp @@ -14,6 +14,8 @@ // with this program (see COPYING). If not, see . // +#include + #include "clua-jsonrpc-machine.h" #include "clua-machine-util.h" #include "clua.h" @@ -21,18 +23,18 @@ namespace cartesi { -/// \brief Deleter for C api jsonrpc stub +/// \brief Deleter for C api jsonrpc connection template <> -void clua_delete(cm_jsonrpc_connection *ptr) { - cm_jsonrpc_destroy_connection(ptr); +void clua_delete(cm_jsonrpc_connection *con) { + cm_jsonrpc_release_connection(con); } /// \brief This is the machine.get_default_machine_config() /// static method implementation. static int jsonrpc_machine_class_get_default_config(lua_State *L) { - const int stubidx = lua_upvalueindex(1); + const int conidx = lua_upvalueindex(1); const int ctxidx = lua_upvalueindex(2); - auto &managed_jsonrpc_connection = clua_check>(L, stubidx, ctxidx); + auto &managed_jsonrpc_connection = clua_check>(L, conidx, ctxidx); const char *config = nullptr; if (cm_jsonrpc_get_default_config(managed_jsonrpc_connection.get(), &config) != 0) { return luaL_error(L, "%s", cm_get_last_error_message()); @@ -57,10 +59,10 @@ static int jsonrpc_machine_class_get_reg_address(lua_State *L) { /// \brief This is the machine.verify_step_uarch() /// static method implementation. static int jsonrpc_machine_class_verify_step_uarch(lua_State *L) { - const int stubidx = lua_upvalueindex(1); + const int conidx = lua_upvalueindex(1); const int ctxidx = lua_upvalueindex(2); lua_settop(L, 5); - auto &managed_jsonrpc_connection = clua_check>(L, stubidx, ctxidx); + auto &managed_jsonrpc_connection = clua_check>(L, conidx, ctxidx); const char *log = clua_check_schemed_json_string(L, 2, "AccessLog", ctxidx); if (!lua_isnil(L, 1) || !lua_isnil(L, 3)) { cm_hash root_hash{}; @@ -81,10 +83,10 @@ static int jsonrpc_machine_class_verify_step_uarch(lua_State *L) { /// \brief This is the machine.verify_reset_uarch() /// static method implementation. static int jsonrpc_machine_class_verify_reset_uarch(lua_State *L) { - const int stubidx = lua_upvalueindex(1); + const int conidx = lua_upvalueindex(1); const int ctxidx = lua_upvalueindex(2); lua_settop(L, 5); - auto &managed_jsonrpc_connection = clua_check>(L, stubidx, ctxidx); + auto &managed_jsonrpc_connection = clua_check>(L, conidx, ctxidx); const char *log = clua_check_schemed_json_string(L, 2, "AccessLog", ctxidx); if (!lua_isnil(L, 1) || !lua_isnil(L, 3)) { cm_hash root_hash{}; @@ -105,10 +107,10 @@ static int jsonrpc_machine_class_verify_reset_uarch(lua_State *L) { /// \brief This is the machine.verify_send_cmio_response() /// static method implementation. static int jsonrpc_machine_class_verify_send_cmio_response(lua_State *L) { - const int stubidx = lua_upvalueindex(1); + const int conidx = lua_upvalueindex(1); const int ctxidx = lua_upvalueindex(2); lua_settop(L, 6); - auto &managed_jsonrpc_connection = clua_check>(L, stubidx, ctxidx); + auto &managed_jsonrpc_connection = clua_check>(L, conidx, ctxidx); const uint16_t reason = static_cast(luaL_checkinteger(L, 1)); size_t length{0}; // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) @@ -151,10 +153,10 @@ static int jsonrpc_machine_tostring(lua_State *L) { /// \brief This is the cartesi.machine() constructor implementation. /// \param L Lua state. static int jsonrpc_machine_ctor(lua_State *L) { - const int stubidx = lua_upvalueindex(1); + const int conidx = lua_upvalueindex(1); const int ctxidx = lua_upvalueindex(2); lua_settop(L, 3); - auto &managed_jsonrpc_connection = clua_check>(L, stubidx, ctxidx); + auto &managed_jsonrpc_connection = clua_check>(L, conidx, ctxidx); auto &managed_machine = clua_push_to(L, clua_managed_cm_ptr(nullptr), ctxidx); const char *runtime_config = !lua_isnil(L, 3) ? clua_check_json_string(L, 3, -1, ctxidx) : nullptr; if (!lua_isstring(L, 2)) { @@ -180,7 +182,7 @@ static const auto jsonrpc_machine_class_meta = cartesi::clua_make_luaL_Reg_array }); /// \brief This is the machine.get_machine() static method implementation. -static int jsonrpc_server_class_get_machine(lua_State *L) { +static int jsonrpc_connection_class_get_machine(lua_State *L) { lua_settop(L, 1); auto &managed_jsonrpc_connection = clua_check>(L, lua_upvalueindex(1), lua_upvalueindex(2)); @@ -193,10 +195,10 @@ static int jsonrpc_server_class_get_machine(lua_State *L) { } /// \brief This is the machine.get_version() static method implementation. -static int jsonrpc_server_class_get_version(lua_State *L) { - const int stubidx = lua_upvalueindex(1); +static int jsonrpc_connection_class_get_version(lua_State *L) { + const int conidx = lua_upvalueindex(1); const int ctxidx = lua_upvalueindex(2); - auto &managed_jsonrpc_connection = clua_check>(L, stubidx, ctxidx); + auto &managed_jsonrpc_connection = clua_check>(L, conidx, ctxidx); const char *version = nullptr; if (cm_jsonrpc_get_version(managed_jsonrpc_connection.get(), &version) != 0) { return luaL_error(L, "%s", cm_get_last_error_message()); @@ -205,18 +207,8 @@ static int jsonrpc_server_class_get_version(lua_State *L) { return 1; } -/// \brief This is the machine.shutdown() static method implementation. -static int jsonrpc_server_class_shutdown(lua_State *L) { - auto &managed_jsonrpc_connection = - clua_check>(L, lua_upvalueindex(1), lua_upvalueindex(2)); - if (cm_jsonrpc_shutdown(managed_jsonrpc_connection.get()) != 0) { - return luaL_error(L, "%s", cm_get_last_error_message()); - } - return 0; -} - /// \brief This is the rebind method implementation. -static int jsonrpc_server_class_rebind(lua_State *L) { +static int jsonrpc_connection_class_rebind(lua_State *L) { auto &managed_jsonrpc_connection = clua_check>(L, lua_upvalueindex(1), lua_upvalueindex(2)); const char *address = luaL_checkstring(L, 1); @@ -233,7 +225,7 @@ static int jsonrpc_server_class_rebind(lua_State *L) { } /// \brief This is the fork method implementation. -static int jsonrpc_server_class_fork(lua_State *L) { +static int jsonrpc_connection_class_fork(lua_State *L) { auto &managed_jsonrpc_connection = clua_check>(L, lua_upvalueindex(1), lua_upvalueindex(2)); const char *address = nullptr; @@ -246,45 +238,108 @@ static int jsonrpc_server_class_fork(lua_State *L) { return 2; } -/// \brief JSONRPC server static methods -static const auto jsonrpc_server_static_methods = cartesi::clua_make_luaL_Reg_array({ - {"get_machine", jsonrpc_server_class_get_machine}, - {"get_version", jsonrpc_server_class_get_version}, - {"shutdown", jsonrpc_server_class_shutdown}, - {"fork", jsonrpc_server_class_fork}, - {"rebind", jsonrpc_server_class_rebind}, +/// \brief JSONRPC connection static methods +static const auto jsonrpc_connection_static_methods = cartesi::clua_make_luaL_Reg_array({ + {"get_machine", jsonrpc_connection_class_get_machine}, + {"get_version", jsonrpc_connection_class_get_version}, + {"fork", jsonrpc_connection_class_fork}, + {"rebind", jsonrpc_connection_class_rebind}, }); -/// \brief This is the jsonrpc.stub() method implementation. -static int mod_stub(lua_State *L) { - const char *remote_address = luaL_checkstring(L, 1); - // Create stub +/// \brief Takes underlying cm_jsonrpc_connection in top of stack and encapsulates it in its Lua interface +static void wrap_jsonrpc_connection(lua_State *L) { + lua_newtable(L); // ccon luacon + lua_newtable(L); // ccon luacon mtab + lua_pushvalue(L, -3); // ccon luacon mtab ccon + lua_pushvalue(L, lua_upvalueindex(1)); // ccon luacon mtab ccon cluactx + luaL_setfuncs(L, jsonrpc_machine_static_methods.data(), 2); // ccon luacon mtab + lua_newtable(L); // ccon luacon mtab mmeta + lua_pushvalue(L, -4); // ccon luacon mtab mmeta ccon + lua_pushvalue(L, lua_upvalueindex(1)); // ccon luacon mtab mmeta ccon cluactx + luaL_setfuncs(L, jsonrpc_machine_class_meta.data(), 2); // ccon luacon mtab mmeta + lua_setmetatable(L, -2); // ccon luacon mtab + lua_setfield(L, -2, "machine"); // ccon luacon + lua_pushvalue(L, -2); // ccon luacon ccon + lua_pushvalue(L, lua_upvalueindex(1)); // ccon luacon ccon cluactx + luaL_setfuncs(L, jsonrpc_connection_static_methods.data(), 2); // ccon luacon + lua_insert(L, -2); // luacon ccon + lua_pop(L, 1); // luacon +} + +static cm_jsonrpc_manage check_cm_jsonrpc_manage(lua_State *L, int idx) { + const char *strwhat = luaL_checkstring(L, idx); + if (strcmp(strwhat, "server") == 0) { + return CM_JSONRPC_MANAGE_SERVER; + } else if (strcmp(strwhat, "machine") == 0) { + return CM_JSONRPC_MANAGE_MACHINE; + } else if (strcmp(strwhat, "none") == 0) { + return CM_JSONRPC_MANAGE_NONE; + } else { + luaL_argerror(L, idx, "expected \"server\", \"machine\", or \"none\""); + return CM_JSONRPC_MANAGE_SERVER; + } +} + +/// \brief This is the jsonrpc.connect() method implementation. +static int mod_connect(lua_State *L) { + // create and push the underlying cm_jsonrpc_connection + const char *address = luaL_checkstring(L, 1); + auto what = check_cm_jsonrpc_manage(L, 2); auto &managed_jsonrpc_connection = clua_push_to(L, clua_managed_cm_ptr(nullptr)); - if (cm_jsonrpc_create_connection(remote_address, &managed_jsonrpc_connection.get()) != 0) { + if (cm_jsonrpc_connect(address, what, &managed_jsonrpc_connection.get()) != 0) { return luaL_error(L, "%s", cm_get_last_error_message()); } - lua_newtable(L); // stub server - lua_newtable(L); // stub server jsonrpc_machine_class - lua_pushvalue(L, -3); // stub server jsonrpc_machine_class stub - lua_pushvalue(L, lua_upvalueindex(1)); // stub server jsonrpc_machine_class stub cluactx - luaL_setfuncs(L, jsonrpc_machine_static_methods.data(), 2); // stub server jsonrpc_machine_class - lua_newtable(L); // stub server jsonrpc_machine_class meta - lua_pushvalue(L, -4); // stub server jsonrpc_machine_class meta stub - lua_pushvalue(L, lua_upvalueindex(1)); // stub server jsonrpc_machine_class meta stub cluactx - luaL_setfuncs(L, jsonrpc_machine_class_meta.data(), 2); // stub server jsonrpc_machine_class meta - lua_setmetatable(L, -2); // stub server jsonrpc_machine_class - lua_setfield(L, -2, "machine"); // stub server - lua_pushvalue(L, -2); // stub server stub - lua_pushvalue(L, lua_upvalueindex(1)); // stub server stub cluactx - luaL_setfuncs(L, jsonrpc_server_static_methods.data(), 2); + // wrap it into its Lua interface + wrap_jsonrpc_connection(L); return 1; } +/// \brief This is the jsonrpc.connect() method implementation. +static int mod_spawn(lua_State *L) { + const char *address = luaL_checkstring(L, 1); + auto what = check_cm_jsonrpc_manage(L, 2); + // create and push the underlying cm_jsonrpc_connection + auto &managed_jsonrpc_connection = clua_push_to(L, clua_managed_cm_ptr(nullptr)); + const char *bound_address = nullptr; + int32_t pid = 0; + if (cm_jsonrpc_spawn(address, what, &managed_jsonrpc_connection.get(), &bound_address, &pid) != 0) { + return luaL_error(L, "%s", cm_get_last_error_message()); + } + // wrap it into its Lua interface + wrap_jsonrpc_connection(L); + lua_pushstring(L, bound_address); + lua_pushinteger(L, pid); + return 3; +} + /// \brief Contents of the jsonrpc module. static const auto mod = cartesi::clua_make_luaL_Reg_array({ - {"stub", mod_stub}, + {"connect", mod_connect}, + {"spawn", mod_spawn}, }); +// jsonrpc.connect() +// return connection object +// jsonrpc.spawn() +// return connection object, bound address, pid +// +// connection object +// machine +// either load or create new machine +// get_machine +// return existing machine object +// get_version +// manage +// rebind +// fork +// __gc/__close call release on connection +// +// machine object +// all methods of normal machine +// get_connection +// destroy? +// __gc/__close call release on connection + int clua_jsonrpc_machine_init(lua_State *L, int ctxidx) { clua_createnewtype>(L, ctxidx); clua_createnewtype>(L, ctxidx); diff --git a/src/clua-machine-util.cpp b/src/clua-machine-util.cpp index e85a7b4b9..d2b5aa5e8 100644 --- a/src/clua-machine-util.cpp +++ b/src/clua-machine-util.cpp @@ -33,7 +33,7 @@ void clua_delete(unsigned char *ptr) { // NOLINT(readability-non- template <> void clua_delete(cm_machine *ptr) { - cm_destroy(ptr, true); // this call should never fail + cm_destroy(ptr); // this call should never fail } template <> diff --git a/src/jsonrpc-connection.h b/src/jsonrpc-connection.h index 5c904d08b..a33e23aaf 100644 --- a/src/jsonrpc-connection.h +++ b/src/jsonrpc-connection.h @@ -30,12 +30,9 @@ namespace cartesi { class jsonrpc_connection final { - boost::asio::io_context m_ioc{1}; // The io_context is required for all I/O - boost::beast::tcp_stream m_stream{m_ioc}; // TCP stream for keep alive connections - boost::container::static_vector m_address{}; - public: - explicit jsonrpc_connection(std::string remote_address); + enum class manage { server, machine, none }; + explicit jsonrpc_connection(std::string remote_address, manage what); jsonrpc_connection(const jsonrpc_connection &other) = delete; jsonrpc_connection(jsonrpc_connection &&other) noexcept = delete; jsonrpc_connection &operator=(const jsonrpc_connection &other) = delete; @@ -50,7 +47,14 @@ class jsonrpc_connection final { void snapshot(void); void commit(void); void rollback(void); + manage get_what_managed(void) const; + +private: void shutdown(void); + boost::asio::io_context m_ioc{1}; // The io_context is required for all I/O + boost::beast::tcp_stream m_stream{m_ioc}; // TCP stream for keep alive connections + boost::container::static_vector m_address{}; + manage m_what_managed{manage::server}; }; } // namespace cartesi diff --git a/src/jsonrpc-machine-c-api.cpp b/src/jsonrpc-machine-c-api.cpp index 87e56b85b..807f3ace5 100644 --- a/src/jsonrpc-machine-c-api.cpp +++ b/src/jsonrpc-machine-c-api.cpp @@ -22,34 +22,182 @@ #include "machine-c-api-internal.h" #include "os-features.h" +#include +#include + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#include "asio-config.h" // must be included before any ASIO header +#include +#pragma GCC diagnostic pop + +using namespace std::string_literals; + +cartesi::jsonrpc_connection::manage convert_from_c(cm_jsonrpc_manage what) { + switch (what) { + case CM_JSONRPC_MANAGE_SERVER: + return cartesi::jsonrpc_connection::manage::server; + case CM_JSONRPC_MANAGE_MACHINE: + return cartesi::jsonrpc_connection::manage::machine; + case CM_JSONRPC_MANAGE_NONE: + return cartesi::jsonrpc_connection::manage::none; + default: + throw std::domain_error("invalid cm_jsonrpc_manage"); + return cartesi::jsonrpc_connection::manage::server; + } +} + static const cartesi::jsonrpc_connection_ptr *convert_from_c(const cm_jsonrpc_connection *con) { if (con == nullptr) { - throw std::invalid_argument("invalid stub"); + throw std::invalid_argument("invalid connection"); } // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) return reinterpret_cast(con); } -cm_error cm_jsonrpc_create_connection(const char *remote_address, cm_jsonrpc_connection **con) try { +static cartesi::i_virtual_machine *convert_from_c(cm_machine *m) { + if (m == nullptr) { + throw std::invalid_argument("invalid machine"); + } + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + return reinterpret_cast(m); +} + +cm_error cm_jsonrpc_connect(const char *address, cm_jsonrpc_manage what, cm_jsonrpc_connection **con) try { + if (address == nullptr) { + throw std::invalid_argument("invalid address"); + } if (con == nullptr) { - throw std::invalid_argument("invalid stub output"); + throw std::invalid_argument("invalid connection output"); } - auto *cpp_connection = new std::shared_ptr( - new cartesi::jsonrpc_connection{remote_address ? remote_address : ""}); + auto cpp_what = convert_from_c(what); + auto *cpp_con = + new cartesi::jsonrpc_connection_ptr(std::make_shared(address, cpp_what)); // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) - *con = reinterpret_cast(cpp_connection); + *con = reinterpret_cast(cpp_con); return cm_result_success(); } catch (...) { + *con = nullptr; return cm_result_failure(); } -void cm_jsonrpc_destroy_connection(const cm_jsonrpc_connection *con) { +static boost::asio::ip::tcp::endpoint address_to_endpoint(const std::string &address) { + try { + const auto pos = address.find_last_of(':'); + const std::string ip = address.substr(0, pos); + const int port = std::stoi(address.substr(pos + 1)); + if (port < 0 || port > 65535) { + throw std::runtime_error{"invalid port"}; + } + return {boost::asio::ip::make_address(ip), static_cast(port)}; + } catch (std::exception &e) { + throw std::runtime_error{"invalid endpoint address \"" + address + "\""}; + } +} + +static std::string endpoint_to_string(const boost::asio::ip::tcp::endpoint &endpoint) { + std::ostringstream ss; + ss << endpoint; + return ss.str(); +} + +cm_error cm_jsonrpc_spawn(const char *address, cm_jsonrpc_manage what, cm_jsonrpc_connection **con, + const char **bound_address, int32_t *pid) try { + if (address == nullptr) { + throw std::invalid_argument("invalid address"); + } + if (con == nullptr) { + throw std::invalid_argument("invalid connection output"); + } + if (bound_address == nullptr) { + throw std::invalid_argument("invalid bound address output"); + } + if (pid == nullptr) { + throw std::invalid_argument("invalid bound address output"); + } + boost::asio::io_context ioc{1}; + boost::asio::ip::tcp::acceptor a(ioc, address_to_endpoint(address)); + // already done by constructor + // a.open(endpoint.protocol()); + // a.set_option(asio::socket_base::reuse_address(true)); + // a.bind(endpoint); + // a.listen(asio::socket_base::max_listen_connections); + static THREAD_LOCAL std::string bound_address_storage = endpoint_to_string(a.local_endpoint()); + sigset_t mask, omask; + sigemptyset(&mask); + sigaddset(&mask, SIGUSR1); + sigaddset(&mask, SIGUSR2); + sigaddset(&mask, SIGALRM); + sigprocmask(SIG_BLOCK, &mask, &omask); + const char *bin = getenv("JSONRPC_REMOTE_CARTESI_MACHINE"); + if (!bin) { + bin = "jsonrpc-remote-cartesi-machine"; + } + const int32_t child = fork(); + if (child == 0) { // child + sigprocmask(SIG_SETMASK, &omask, nullptr); + char server_fd[256] = ""; + snprintf(server_fd, std::size(server_fd), "--server-fd=%d", a.native_handle()); + char *args[] = {const_cast(bin), server_fd, const_cast("--setpgid"), + const_cast("--sigusr1"), nullptr}; + if (execvp(bin, args) < 0) { + kill(getppid(), SIGUSR2); // notify parent that exec failed + exit(1); + }; + } else if (child > 0) { // parent and fork() succeeded + // change child to its own process group + setpgid(child, child); + struct itimerval value, ovalue; + value.it_interval.tv_sec = 0; + value.it_interval.tv_usec = 0; + value.it_value.tv_sec = 15; + value.it_value.tv_usec = 0; + setitimer(ITIMER_REAL, &value, &ovalue); + int sig = 0; + sigwait(&mask, &sig); + // get rid of our copy of socket + a.close(); + // restore previous timer + setitimer(ITIMER_REAL, &ovalue, nullptr); + // restore previous mask + sigprocmask(SIG_SETMASK, &omask, nullptr); + if (sig == SIGALRM) { // child didn't signal us before alarm + kill(child, SIGTERM); + throw std::runtime_error{"child process unresponsive"}; + } + if (sig == SIGUSR2) { // child signaled us that it failed to exec + // child will have exited on its own + throw std::runtime_error{"failed to run '"s + bin + "'"s}; + } + assert(sig == SIGUSR1); + // child signaled us that everything is fine + *bound_address = bound_address_storage.c_str(); + *pid = child; + auto ret = cm_jsonrpc_connect(*bound_address, what, con); + if (ret < 0) { // and yet we failed to connect + kill(child, SIGTERM); + *bound_address = nullptr; + *pid = 0; + } + return ret; + } else { // fork failed + sigprocmask(SIG_SETMASK, &omask, nullptr); + throw std::system_error{errno, std::generic_category(), "fork failed"}; + } + return cm_result_success(); +} catch (...) { + *con = nullptr; + *bound_address = nullptr; + return cm_result_failure(); +} + +void cm_jsonrpc_release_connection(const cm_jsonrpc_connection *con) { if (con == nullptr) { return; } // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) - const auto *mgr_wrapper = reinterpret_cast(con); - delete mgr_wrapper; + const auto *cpp_con = reinterpret_cast(con); + delete cpp_con; } cm_error cm_jsonrpc_create_machine(const cm_jsonrpc_connection *con, const char *config, const char *runtime_config, @@ -102,6 +250,21 @@ cm_error cm_jsonrpc_get_machine(const cm_jsonrpc_connection *con, cm_machine **n return cm_result_failure(); } +CM_API cm_error cm_jsonrpc_get_connection(cm_machine *m, const cm_jsonrpc_connection **con) try { + auto *cpp_machine = convert_from_c(m); + cartesi::jsonrpc_virtual_machine *cpp_json_machine = dynamic_cast(cpp_machine); + if (!cpp_json_machine) { + throw std::invalid_argument("not a remote machine"); + } + auto *cpp_con = new cartesi::jsonrpc_connection_ptr(cpp_json_machine->get_connection()); + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + *con = reinterpret_cast(cpp_con); + return cm_result_success(); +} catch (...) { + *con = nullptr; + return cm_result_failure(); +} + cm_error cm_jsonrpc_get_default_config(const cm_jsonrpc_connection *con, const char **config) try { if (config == nullptr) { throw std::invalid_argument("invalid config output"); @@ -224,14 +387,6 @@ cm_error cm_jsonrpc_get_version(const cm_jsonrpc_connection *con, const char **v return cm_result_failure(); } -cm_error cm_jsonrpc_shutdown(const cm_jsonrpc_connection *con) try { - const auto *cpp_connection = convert_from_c(con); - cartesi::jsonrpc_virtual_machine::shutdown(*cpp_connection); - return cm_result_success(); -} catch (...) { - return cm_result_failure(); -} - cm_error cm_jsonrpc_verify_send_cmio_response(const cm_jsonrpc_connection *con, uint16_t reason, const uint8_t *data, uint64_t length, const cm_hash *root_hash_before, const char *log, const cm_hash *root_hash_after) try { if (log == nullptr) { diff --git a/src/jsonrpc-machine-c-api.h b/src/jsonrpc-machine-c-api.h index 046afeb55..a2dc4b511 100644 --- a/src/jsonrpc-machine-c-api.h +++ b/src/jsonrpc-machine-c-api.h @@ -28,11 +28,11 @@ extern "C" { // ----------------------------------------------------------------------------- /// \brief Constants. -typedef enum cm_jsonrpc_cleanup { - CM_JSONRPC_CLEANUP_SERVER = 0, ///< Release machine and shutdown server when closing connection - CM_JSONRPC_CLEANUP_MACHINE = 1, ///< Release machine but leave server running - CM_JSONRPC_CLEANUP_NONE = 2 ///< Leave machine and server alone -} cm_jsonrpc_cleanup; +typedef enum cm_jsonrpc_manage { + CM_JSONRPC_MANAGE_SERVER = 0, ///< Destroy machine and shutdown server when connection is released + CM_JSONRPC_MANAGE_MACHINE = 1, ///< Destroy machine but leave server running + CM_JSONRPC_MANAGE_NONE = 2 ///< Leave machine and server alone +} cm_jsonrpc_manage; /// \brief Handle of the JSONRPC connection. /// \details It's used only as an opaque handle to pass JSONRPC connection through the C API. @@ -48,35 +48,30 @@ typedef struct cm_jsonrpc_connection cm_jsonrpc_connection; /// \brief Connects to an existing JSONRPC remote machine server. /// \param address Address of the remote machine server to connect to. -/// \param cleanup What to cleanup when cm_jsonrpc_release() is called on connection. +/// \param what What to take ownership of and mange when establishing connection. /// \param con If function succeeds, receives new JSONRPC connection. Set to NULL on failure. /// \returns 0 for success, non zero code for error. -CM_API cm_error cm_jsonrpc_connect(const char *address, cm_jsonrpc_connection **con); +CM_API cm_error cm_jsonrpc_connect(const char *address, cm_jsonrpc_manage what, cm_jsonrpc_connection **con); /// \brief Spawns a new JSONRPC remote machine server and connect to it. /// \param address Address (in local host) to bind the new JSONRPC remote machine server. +/// \param what What to take ownership of and mange when establishing connection. /// \param con If function succeeds, receives new JSONRPC connection. Set to NULL on failure. /// \param bound_address_bound Receives the address that the remote server actually bound to, guaranteed to remain valid /// only until the the next time this same function is called again on the same thread. Set to NULL on failure. /// \param pid If function suceeds, receives the forked child process id. Set to 0 on failure. /// \details If the jsonrpc-remote-cartesi-machine executable is not in the path, the /// the environment variable JSONRPC_REMOTE_CARTESI_MACHINE should point to the executable. -CM_API cm_error cm_jsonrpc_spawn(const char *address, cm_jsonrpc_connection **con, +CM_API cm_error cm_jsonrpc_spawn(const char *address, cm_jsonrpc_manage what, cm_jsonrpc_connection **con, const char **bound_address, int32_t *pid); -/// \brief Configure resources managed by connection -/// \param con Pointer to a valid JSONRPC connection. -/// \param cleanup What to cleanup when the last reference to the connection is released by cm_jsonrpc_release(). -/// \returns 0 for success, non zero code for error. -CM_API cm_error cm_jsonrpc_manage(const cm_jsonrpc_connection *con, cm_jsonrpc_cleanup cleanup); - /// \brief Releases a reference to a JSONRPC connection to remote machine server. /// \param con Pointer a JSONRPC connection (can be NULL). /// \returns 0 for success, non zero code for error. /// \details When the last reference to the connection is released, the function attempts to follow -/// the last cm_jsonrpc_cleanup instructions specified by cm_jsonrpc_manage(). +/// the cm_jsonrpc_manage instructions specified at cm_jsonrpc_connect() or cm_jsonrpc_spawn(). /// The connection object itself is deallocated immediately and its pointer must not be used after this call. -CM_API void cm_jsonrpc_release(const cm_jsonrpc_connection *con); +CM_API void cm_jsonrpc_release_connection(const cm_jsonrpc_connection *con); /// \brief Forks the remote server. /// \param con Pointer to a valid JSONRPC connection. @@ -126,7 +121,7 @@ CM_API cm_error cm_jsonrpc_get_reg_address(const cm_jsonrpc_connection *con, cm_ /// \brief Creates a remote machine instance. /// \param con Pointer to a valid JSONRPC connection. /// \param config Machine configuration as a JSON string. -/// \param runtime_config Machine runtime configuration as a JSON string, it can be NULL. +/// \param runtime_config Machine runtime configuration as a JSON string (can be NULL). /// \param m Receives the pointer to new remote machine instance. /// \returns 0 for success, non zero code for error. /// \details The machine instance holds its own reference to the connection. @@ -136,7 +131,7 @@ CM_API cm_error cm_jsonrpc_create_machine(const cm_jsonrpc_connection *con, cons /// \brief Creates a remote machine instance from previously stored directory in the remote server. /// \param con Pointer to a valid JSONRPC connection. /// \param dir Directory where previous machine is stored. -/// \param runtime_config Machine runtime configuration as a JSON string, it can be NULL. +/// \param runtime_config Machine runtime configuration as a JSON string (can be NULL). /// \param m Receives the pointer to new remote machine instance. /// \returns 0 for success, non zero code for error. /// \details The machine instance holds its own reference to the connection. diff --git a/src/jsonrpc-remote-machine.cpp b/src/jsonrpc-remote-machine.cpp index 570d6cc36..0aa0cf3fc 100644 --- a/src/jsonrpc-remote-machine.cpp +++ b/src/jsonrpc-remote-machine.cpp @@ -259,7 +259,7 @@ struct http_handler : std::enable_shared_from_this { signals(ioc), local_endpoint(acceptor.local_endpoint()), acceptor(std::move(acceptor)) { - SLOG(info) << "remote machine bound to " << local_endpoint; + SLOG(info) << "remote machine server bound to " << local_endpoint; } // Installs all handlers that should stop the HTTP server @@ -1588,6 +1588,9 @@ where options are the proccess group id is the same as the process id of the server the server and all its children can be signaled via this process group id + --sigusr1 + send SIGUSR1 to parent process when ready + --log-level= sets the log level can be @@ -1637,6 +1640,7 @@ int main(int argc, char *argv[]) try { const char *server_address = nullptr; int server_fd = -1; bool newpg = false; + bool sigusr1 = false; const char *log_level = nullptr; const char *program_name = PROGRAM_NAME; @@ -1653,6 +1657,8 @@ int main(int argc, char *argv[]) try { ; } else if (strcmp(argv[i], "--setpgid") == 0) { newpg = true; + } else if (strcmp(argv[i], "--sigusr1") == 0) { + sigusr1 = true; } else if (strcmp(argv[i], "--help") == 0) { help(program_name); exit(0); @@ -1665,12 +1671,16 @@ int main(int argc, char *argv[]) try { // create a new process group and become its leader if (newpg) { setpgid(0, 0); - SLOG(info) << "remote machine now has pgid:" << getpgid(0); + SLOG(info) << "remote machine server now has pgid:" << getpgid(0); + } + + if (sigusr1) { + kill(getppid(), SIGUSR1); } init_logger(log_level); - SLOG(info) << "remote machine version is " << server_version_major << "." << server_version_minor << "." + SLOG(info) << "remote machine server version is " << server_version_major << "." << server_version_minor << "." << server_version_patch; install_restart_signal_handlers(); @@ -1686,18 +1696,8 @@ int main(int argc, char *argv[]) try { } SLOG(info) << "attempting to inherit fd " << server_fd << " from parent"; // check socket is listening and is of right domain and type - int listen = 0; - socklen_t len = sizeof(listen); - if (getsockopt(server_fd, SOL_SOCKET, SO_ACCEPTCONN, &listen, &len) < 0) { - SLOG(fatal) << "getsockopt failed on inherited fd: " << strerror(errno); - exit(1); - } - if (!listen) { - SLOG(fatal) << "inherited is fd not a listening socket"; - exit(1); - } struct sockaddr_in fd_addr; - len = sizeof(fd_addr); + socklen_t len = sizeof(fd_addr); memset(&fd_addr, 0, len); if (getsockname(server_fd, reinterpret_cast(&fd_addr), &len) < 0) { SLOG(fatal) << "getsockname failed on inherited fd: " << strerror(errno); @@ -1707,6 +1707,21 @@ int main(int argc, char *argv[]) try { SLOG(fatal) << "inherited fd is not an inet/inet6 domain socket"; exit(1); } + int listen = 0; + len = sizeof(listen); + if (getsockopt(server_fd, SOL_SOCKET, SO_ACCEPTCONN, &listen, &len) < 0) { + auto copy = errno; + if (copy != ENOPROTOOPT) { + SLOG(fatal) << "getsockopt failed on inherited fd: " << strerror(errno); + exit(1); + } else { // test is not supported in platform (e.g. macOS), so we just hope for the best + listen = 1; + } + } + if (!listen) { + SLOG(fatal) << "inherited is fd not a listening socket"; + exit(1); + } int type = 0; len = sizeof(type); if (getsockopt(server_fd, SOL_SOCKET, SO_TYPE, &type, &len) < 0) { @@ -1742,7 +1757,7 @@ int main(int argc, char *argv[]) try { // e.g, there is no more clients connected and the handler is not accepting new connections. ioc.run(); - SLOG(trace) << "remote machine terminated"; + SLOG(trace) << "remote machine server exiting"; return 0; } catch (std::exception &e) { SLOG(fatal) << "caught exception: " << e.what(); diff --git a/src/jsonrpc-virtual-machine.cpp b/src/jsonrpc-virtual-machine.cpp index 8bca3209b..68a83076c 100644 --- a/src/jsonrpc-virtual-machine.cpp +++ b/src/jsonrpc-virtual-machine.cpp @@ -255,13 +255,23 @@ void jsonrpc_request(beast::tcp_stream &stream, const std::string &remote_addres namespace cartesi { -jsonrpc_connection::jsonrpc_connection(std::string remote_address) { - m_address.push_back(std::move(remote_address)); +jsonrpc_connection::jsonrpc_connection(std::string address, manage what) { + m_address.push_back(std::move(address)); + m_what_managed = what; // Install handler to ignore SIGPIPE lest we crash when a server closes a connection os_disable_sigpipe(); } jsonrpc_connection::~jsonrpc_connection() { + // If configured to detroy machine but not server, do it + if (m_what_managed == manage::machine) { + bool result = false; + jsonrpc_request(get_stream(), get_remote_address(), "machine.destroy", std::tie(), result, false); + } + // If configured to shutdown server, do it + if (m_what_managed == manage::server) { + shutdown(); + } // Gracefully close any established keep alive connection if (m_stream.socket().is_open()) { beast::error_code ec; @@ -355,6 +365,10 @@ bool jsonrpc_connection::is_shutdown(void) const { return m_address.empty(); } +auto jsonrpc_connection::get_what_managed(void) const -> manage { + return m_what_managed; +} + jsonrpc_virtual_machine::jsonrpc_virtual_machine(jsonrpc_connection_ptr con) : m_connection(std::move(con)) {} jsonrpc_virtual_machine::jsonrpc_virtual_machine(jsonrpc_connection_ptr con, const std::string &directory, @@ -373,7 +387,12 @@ jsonrpc_virtual_machine::jsonrpc_virtual_machine(jsonrpc_connection_ptr con, con std::tie(config, runtime), result); } -jsonrpc_virtual_machine::~jsonrpc_virtual_machine(void) = default; +jsonrpc_virtual_machine::~jsonrpc_virtual_machine(void) { + auto what = m_connection->get_what_managed(); + if (what != jsonrpc_connection::manage::none) { + destroy(); + } +} machine_config jsonrpc_virtual_machine::do_get_initial_config(void) const { machine_config result; @@ -394,13 +413,13 @@ semantic_version jsonrpc_virtual_machine::get_version(const jsonrpc_connection_p return result; } +jsonrpc_connection_ptr jsonrpc_virtual_machine::get_connection(void) const { + return m_connection; +} + #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-parameter" -void jsonrpc_virtual_machine::shutdown(const jsonrpc_connection_ptr &con) { - con->shutdown(); -} - void jsonrpc_virtual_machine::verify_step_uarch_log(const jsonrpc_connection_ptr &con, const access_log &log) { bool result = false; jsonrpc_request(con->get_stream(), con->get_remote_address(), "machine.verify_step_uarch_log", std::tie(log), diff --git a/src/jsonrpc-virtual-machine.h b/src/jsonrpc-virtual-machine.h index a48ae72e9..06215a3ba 100644 --- a/src/jsonrpc-virtual-machine.h +++ b/src/jsonrpc-virtual-machine.h @@ -52,9 +52,9 @@ class jsonrpc_virtual_machine final : public i_virtual_machine { jsonrpc_virtual_machine &operator=(jsonrpc_virtual_machine &&other) noexcept = delete; ~jsonrpc_virtual_machine() override; - static semantic_version get_version(const jsonrpc_connection_ptr &con); + jsonrpc_connection_ptr get_connection(void) const; - static void shutdown(const jsonrpc_connection_ptr &con); + static semantic_version get_version(const jsonrpc_connection_ptr &con); static machine_config get_default_config(const jsonrpc_connection_ptr &con); diff --git a/src/machine-c-api.cpp b/src/machine-c-api.cpp index a256d9cd5..ef307fd30 100644 --- a/src/machine-c-api.cpp +++ b/src/machine-c-api.cpp @@ -548,12 +548,9 @@ cm_error cm_replace_memory_range(cm_machine *m, uint64_t start, uint64_t length, return cm_result_failure(); } -cm_error cm_destroy(cm_machine *m, bool keep_machine) try { +cm_error cm_destroy(cm_machine *m) try { if (m != nullptr) { auto *cpp_machine = convert_from_c(m); - if (!keep_machine) { - cpp_machine->destroy(); - } delete cpp_machine; } return cm_result_success(); diff --git a/src/machine-c-api.h b/src/machine-c-api.h index abec236b7..eebdae4b6 100644 --- a/src/machine-c-api.h +++ b/src/machine-c-api.h @@ -341,12 +341,11 @@ CM_API cm_error cm_create(const char *config, const char *runtime_config, cm_mac /// \brief Destroys a machine. /// \param m Pointer to the existing machine instance (can be NULL). -/// \param keep_machine If true, the machine is not destroyed in the remote server. /// \returns 0 for success, non zero code for error. /// \details The machine is deallocated and its pointer must not be used after this call. -/// This method always succeeds for local machines, but may fail for remote machines when -/// keep machine is false. In case of failure it must be called again to free resources. -CM_API cm_error cm_destroy(cm_machine *m, bool keep_machine); +/// This method always succeeds for local machines, but may fail for remote machines. +/// In case of failure it must be called again to free resources. +CM_API cm_error cm_destroy(cm_machine *m); /// \brief Loads a new machine instance from a previously stored directory. /// \param dir Directory where previous machine is stored.