diff --git a/CMakeLists.txt b/CMakeLists.txt index 4da036e..302934b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -65,10 +65,10 @@ find_package(Protobuf REQUIRED) pkg_check_modules(SDL2 sdl2 REQUIRED) pkg_check_modules(SDL2_IMAGE SDL2_image REQUIRED) -pkg_check_modules(DBUS_CPP dbus-cpp REQUIRED) pkg_check_modules(DBUS dbus-1 REQUIRED) pkg_check_modules(LXC lxc REQUIRED) pkg_check_modules(PROPERTIES_CPP properties-cpp REQUIRED) +pkg_check_modules(LIBSYSTEMD libsystemd REQUIRED) # FIXME mir support is currently broken due to mir's API # being broken with recent landings in Ubuntu 16.04 # pkg_check_modules(MIRCLIENT mirclient) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7253a3a..3e2817b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -11,6 +11,7 @@ include_directories( ${PROPERTIES_CPP_INCLUDE_DIRS} ${LXC_INCLUDE_DIRS} ${MIRCLIENT_INCLUDE_DIRS} + ${LIBSYSTEMD_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/src @@ -195,11 +196,16 @@ set(SOURCES anbox/bridge/platform_api_skeleton.cpp anbox/bridge/android_api_stub.cpp + anbox/dbus/bus.cpp + anbox/dbus/bus.h + anbox/dbus/sd_bus_helpers.h anbox/dbus/interface.h - anbox/dbus/codecs.h anbox/dbus/skeleton/service.cpp + anbox/dbus/skeleton/service.h anbox/dbus/skeleton/application_manager.cpp + anbox/dbus/skeleton/application_manager.h anbox/dbus/stub/application_manager.cpp + anbox/dbus/stub/application_manager.h anbox/application/launcher_storage.cpp anbox/application/database.cpp @@ -210,6 +216,8 @@ set(SOURCES anbox/cmds/container_manager.cpp anbox/cmds/launch.cpp anbox/cmds/system_info.cpp + anbox/cmds/wait_ready.cpp + anbox/cmds/wait_ready.h anbox/utils/environment_file.cpp @@ -233,6 +241,8 @@ target_link_libraries(anbox-core ${LXC_LIBRARIES} ${MIRCLIENT_LDFLAGS} ${MIRCLIENT_LIBRARIES} + ${LIBSYSTEMD_LDFLAGS} + ${LIBSYSTEMD_LIBRARIES} pthread process-cpp emugl_common diff --git a/src/anbox/cmds/launch.cpp b/src/anbox/cmds/launch.cpp index e5cc5a9..5ffbd57 100644 --- a/src/anbox/cmds/launch.cpp +++ b/src/anbox/cmds/launch.cpp @@ -16,29 +16,30 @@ */ #include "anbox/cmds/launch.h" -#include "anbox/common/wait_handle.h" #include "anbox/dbus/stub/application_manager.h" -#include "anbox/common/dispatcher.h" +#include "anbox/dbus/interface.h" #include "anbox/ui/splash_screen.h" #include "anbox/system_configuration.h" -#include "anbox/runtime.h" #include "anbox/logger.h" -#include - -#include - #include "core/posix/exec.h" #include "core/posix/fork.h" #include "core/posix/signal.h" +#include + +#include +#include + namespace fs = boost::filesystem; namespace { -const boost::posix_time::seconds max_wait_timeout{240}; -const int max_restart_attempts{3}; -const std::chrono::seconds restart_interval{5}; +constexpr unsigned int max_session_mgr_wait_attempts{10}; +const std::chrono::seconds session_mgr_wait_interval{5}; +constexpr unsigned int max_dbus_service_wait_attempts{10}; +const std::chrono::seconds dbus_service_wait_interval{5}; +#if 0 static int redirect_to_null(int flags, int fd) { int fd2; if ((fd2 = open("/dev/null", flags)) < 0) @@ -53,6 +54,73 @@ static int redirect_to_null(int flags, int fd) { close(fd2); return fd; } +#endif +} // namespace + +bool anbox::cmds::Launch::launch_session_manager() { + std::vector args = {"session-manager"}; + std::map env; + core::posix::this_process::env::for_each([&](const std::string &name, const std::string &value) { + env.insert({name, value}); + }); + + const auto exe_path = utils::process_get_exe_path(::getpid()); + if (!fs::exists(exe_path)) { + ERROR("Can't find correct anbox executable to run. Found %s but does not exist", exe_path); + return false; + } + + try { + auto flags = core::posix::StandardStream::empty; + auto child = core::posix::fork([&]() { + +#if 0 + // We redirect all in/out/err to /dev/null as they can't be seen + // anywhere. All logging output will directly go to syslog as we + // will become a session leader below which will get us rid of a + // controlling terminal. + if (redirect_to_null(O_RDONLY, 0) < 0 || + redirect_to_null(O_WRONLY, 1) < 0 || + redirect_to_null(O_WRONLY, 2) < 0) { + ERROR("Failed to redirect stdout/stderr/stdin: %s", strerror(errno)); + return core::posix::exit::Status::failure; + } + +#endif + + // As we forked one time already we're sure that our process is + // not the session leader anymore so we can safely become the + // new one and lead the process group. + if (setsid() < 0) { + ERROR("Failed to become new session leader: %s", strerror(errno)); + return core::posix::exit::Status::failure; + } + + umask(0077); + + if (chdir("/") < 0) { + ERROR("Failed to change current directory: %s", strerror(errno)); + return core::posix::exit::Status::failure; + } + + auto grandchild = core::posix::exec(exe_path, args, env, flags); + grandchild.dont_kill_on_cleanup(); + return core::posix::exit::Status::success; + }, flags); + + // We don't wait for the grandchild but the child as we use double forking + // here to break through the process hierarchy and make the grandchild a + // direct child of the init process so it keeps running on its own and + // indepent of our short living process here. + child.wait_for(core::posix::wait::Flags::untraced); + + DEBUG("Started session manager, will now try to connect .."); + } + catch (...) { + ERROR("Failed to start session manager instance"); + } + + return true; } bool anbox::cmds::Launch::try_launch_activity(const std::shared_ptr &stub) { @@ -96,6 +164,7 @@ anbox::cmds::Launch::Launch() cli::Description{"Use system instead of session DBus"}, use_system_dbus_)); + action([this](const cli::Command::Context&) { if (!intent_.valid()) { ERROR("The intent you provided is invalid. Please provide a correct launch intent."); @@ -104,144 +173,54 @@ anbox::cmds::Launch::Launch() return EXIT_FAILURE; } - auto trap = core::posix::trap_signals_for_process({core::posix::Signal::sig_term, core::posix::Signal::sig_int}); - trap->signal_raised().connect([trap](const core::posix::Signal& signal) { - INFO("Signal %i received. Good night.", static_cast(signal)); - trap->stop(); - }); - - auto rt = Runtime::create(); - - auto bus_type = core::dbus::WellKnownBus::session; + auto bus_type = anbox::dbus::Bus::Type::Session; if (use_system_dbus_) - bus_type = core::dbus::WellKnownBus::system; - - auto bus = std::make_shared(bus_type); - bus->install_executor(core::dbus::asio::make_executor(bus, rt->service())); + bus_type = anbox::dbus::Bus::Type::System; + auto bus = std::make_shared(bus_type); std::shared_ptr ss; + if (!bus->has_service_with_name(dbus::interface::Service::name())) { + DEBUG("Session manager is not yet running, trying to start it"); - // Instead of relying on the user session init system to start our - // session manager process we also attempt to start it on our own - // if not already running. This will help to mitigate problems with - // a crashing or a not yet started session manager instance. - std::shared_ptr stub; - for (auto n = 0; n < max_restart_attempts; n++) { - try { - stub = dbus::stub::ApplicationManager::create_for_bus(bus); - break; - } catch (std::exception &err) { - WARNING("Anbox session manager service isn't running, trying to start it."); + if (!launch_session_manager()) + return EXIT_FAILURE; - // Give us a splash screen as long as we're trying to connect - // with the session manager so the user knows something is - // happening after he started Anbox. - if (!ss) - ss = std::make_shared(); - - std::vector args = {"session-manager"}; - - std::map env; - core::posix::this_process::env::for_each([&](const std::string &name, const std::string &value) { - env.insert({name, value}); - }); - - const auto exe_path = utils::process_get_exe_path(::getpid()); - if (!fs::exists(exe_path)) { - ERROR("Can't find correct anbox executable to run. Found %s but does not exist", exe_path); - return EXIT_FAILURE; - } - - try { - auto flags = core::posix::StandardStream::empty; - auto child = core::posix::fork([&]() { - // We redirect all in/out/err to /dev/null as they can't be seen - // anywhere. All logging output will directly go to syslog as we - // will become a session leader below which will get us rid of a - // controlling terminal. - if (redirect_to_null(O_RDONLY, 0) < 0 || - redirect_to_null(O_WRONLY, 1) < 0 || - redirect_to_null(O_WRONLY, 2) < 0) { - ERROR("Failed to redirect stdout/stderr/stdin: %s", strerror(errno)); - return core::posix::exit::Status::failure; - } - - // As we forked one time already we're sure that our process is - // not the session leader anymore so we can safely become the - // new one and lead the process group. - if (setsid() < 0) { - ERROR("Failed to become new session leader: %s", strerror(errno)); - return core::posix::exit::Status::failure; - } - - umask(0077); - - if (chdir("/") < 0) { - ERROR("Failed to change current directory: %s", strerror(errno)); - return core::posix::exit::Status::failure; - } - - auto grandchild = core::posix::exec(exe_path, args, env, flags); - grandchild.dont_kill_on_cleanup(); - return core::posix::exit::Status::success; - }, flags); - - // We don't wait for the grandchild but the child as we use double forking - // here to break through the process hierarchy and make the grandchild a - // direct child of the init process so it keeps running on its own and - // indepent of our short living process here. - child.wait_for(core::posix::wait::Flags::untraced); - - DEBUG("Started session manager, will now try to connect .."); - } - catch (...) { - ERROR("Failed to start session manager instance"); - } - - std::this_thread::sleep_for(restart_interval); - } + // Give us a splash screen as long as we're trying to connect + // with the session manager so the user knows something is + // happening after he started Anbox. + ss = std::make_shared(); } - if (!stub) { - ERROR("Couldn't get a connection with the session manager"); + unsigned int n = 0; + while (n < max_dbus_service_wait_attempts) { + if (bus->has_service_with_name(dbus::interface::Service::name())) + break; + + std::this_thread::sleep_for(dbus_service_wait_interval); + n++; + } + + auto app_mgr = dbus::stub::ApplicationManager::create_for_bus(bus); + n = 0; + while (n < max_session_mgr_wait_attempts) { + app_mgr->update_properties(); + if (app_mgr->ready().get()) + break; + + std::this_thread::sleep_for(session_mgr_wait_interval); + n++; + } + + if (!app_mgr->ready()) { + ERROR("Session manager failed to become ready"); return EXIT_FAILURE; } - bool success = false; - auto dispatcher = anbox::common::create_dispatcher_for_runtime(rt); - dispatcher->dispatch([&]() { - if (stub->ready()) { - ss.reset(); - success = try_launch_activity(stub); - trap->stop(); - return; - } - - DEBUG("Android hasn't fully booted yet. Waiting a bit .."); - - stub->ready().changed().connect([&](bool ready) { - if (!ready) - return; - ss.reset(); - success = try_launch_activity(stub); - trap->stop(); - }); - }); - - boost::asio::deadline_timer timer(rt->service()); - timer.expires_from_now(max_wait_timeout); - timer.async_wait([&](const boost::system::error_code&) { - WARNING("Stopped waiting as we've already waited for too long. Something"); - WARNING("is wrong with your setup or the container has failed to boot."); - WARNING("If you think you found a bug please don't hesitate to file on"); - WARNING("at https://github.com/anbox/anbox/issues/new"); - trap->stop(); - }); - - rt->start(); - trap->run(); - rt->stop(); + // If we have a splash screen now is the time to drop it as we're + // going to launch the real application now. + ss.reset(); + const auto success = try_launch_activity(app_mgr); return success ? EXIT_SUCCESS : EXIT_FAILURE; }); } diff --git a/src/anbox/cmds/launch.h b/src/anbox/cmds/launch.h index b555c41..4797aea 100644 --- a/src/anbox/cmds/launch.h +++ b/src/anbox/cmds/launch.h @@ -34,6 +34,7 @@ class Launch : public cli::CommandWithFlagsAndAction { Launch(); private: + bool launch_session_manager(); bool try_launch_activity(const std::shared_ptr &stub); android::Intent intent_; diff --git a/src/anbox/cmds/session_manager.cpp b/src/anbox/cmds/session_manager.cpp index c8f27ff..02d24cd 100644 --- a/src/anbox/cmds/session_manager.cpp +++ b/src/anbox/cmds/session_manager.cpp @@ -33,6 +33,7 @@ #include "anbox/common/dispatcher.h" #include "anbox/system_configuration.h" #include "anbox/container/client.h" +#include "anbox/dbus/bus.h" #include "anbox/dbus/skeleton/service.h" #include "anbox/input/manager.h" #include "anbox/logger.h" @@ -49,8 +50,6 @@ #include -#include -#include #pragma GCC diagnostic pop namespace fs = boost::filesystem; @@ -140,13 +139,12 @@ anbox::cmds::SessionManager::SessionManager() if (!standalone_) { container_ = std::make_shared(rt); container_->register_terminate_handler([&]() { - WARNING("Lost connection to container manager, terminating."); - trap->stop(); - }); + WARNING("Lost connection to container manager, terminating."); + trap->stop(); + }); } auto input_manager = std::make_shared(rt); - auto android_api_stub = std::make_shared(); auto display_frame = graphics::Rect::Invalid; @@ -244,18 +242,21 @@ anbox::cmds::SessionManager::SessionManager() {"/dev/fuse", "/dev/fuse"}, }; - dispatcher->dispatch([&]() { container_->start(container_configuration); }); + dispatcher->dispatch([&]() { + DEBUG("Starting container"); + container_->start(container_configuration); + }); } - auto bus_type = core::dbus::WellKnownBus::session; + auto bus_type = anbox::dbus::Bus::Type::Session; if (use_system_dbus_) - bus_type = core::dbus::WellKnownBus::system; - - auto bus = std::make_shared(bus_type); - bus->install_executor(core::dbus::asio::make_executor(bus, rt->service())); + bus_type = anbox::dbus::Bus::Type::System; + auto bus = std::make_shared(bus_type); auto skeleton = anbox::dbus::skeleton::Service::create_for_bus(bus, app_manager); + bus->run_async(); + rt->start(); trap->run(); diff --git a/src/anbox/cmds/session_manager.h b/src/anbox/cmds/session_manager.h index bbd2d04..a273fe8 100644 --- a/src/anbox/cmds/session_manager.h +++ b/src/anbox/cmds/session_manager.h @@ -24,8 +24,6 @@ #include #include -#include - #include "anbox/graphics/gl_renderer_server.h" #include "anbox/graphics/rect.h" diff --git a/src/anbox/cmds/system_info.cpp b/src/anbox/cmds/system_info.cpp index 60d77eb..9b29a7b 100644 --- a/src/anbox/cmds/system_info.cpp +++ b/src/anbox/cmds/system_info.cpp @@ -148,7 +148,7 @@ class SystemInformation { EGLConfig config; int n; if (s_egl.eglChooseConfig(display, config_attribs, &config, 1, &n) && n > 0) { - GLint attribs[] = { EGL_CONTEXT_CLIENT_VERSION, 1, EGL_NONE}; + GLint attribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE}; auto context = s_egl.eglCreateContext(display, config, nullptr, attribs); if (context != EGL_NO_CONTEXT) { // We require surfaceless-context support here for now. If eglMakeCurrent fails diff --git a/src/anbox/cmds/wait_ready.cpp b/src/anbox/cmds/wait_ready.cpp new file mode 100644 index 0000000..9e62ce6 --- /dev/null +++ b/src/anbox/cmds/wait_ready.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2016 Canonical, Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Authored by: Thomas Voß + * + */ + +#include "anbox/cmds/wait_ready.h" +#include "anbox/dbus/stub/application_manager.h" + +namespace { +constexpr const unsigned int max_wait_attempts{30}; +} + +anbox::cmds::WaitReady::WaitReady() + : CommandWithFlagsAndAction{ + cli::Name{"wait-ready"}, cli::Usage{"wait-ready"}, + cli::Description{"Wait until the Android system has successfully booted"}} { + + flag(cli::make_flag(cli::Name{"use-system-dbus"}, + cli::Description{"Use system instead of session DBus"}, + use_system_dbus_)); + + action([this](const cli::Command::Context&) { + auto bus_type = anbox::dbus::Bus::Type::Session; + if (use_system_dbus_) + bus_type = anbox::dbus::Bus::Type::System; + auto bus = std::make_shared(bus_type); + + auto stub = dbus::stub::ApplicationManager::create_for_bus(bus); + + unsigned int n = 0; + while (n < max_wait_attempts) { + stub->update_properties(); + if (stub->ready().get()) + return EXIT_SUCCESS; + + std::this_thread::sleep_for(std::chrono::seconds{1}); + n++; + } + + return EXIT_FAILURE; + }); +} diff --git a/src/anbox/cmds/wait_ready.h b/src/anbox/cmds/wait_ready.h new file mode 100644 index 0000000..fa61ea6 --- /dev/null +++ b/src/anbox/cmds/wait_ready.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2018 Simon Fels + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + * + */ + +#ifndef ANBOX_CMDS_WAIT_READY_H_ +#define ANBOX_CMDS_WAIT_READY_H_ + +#include +#include +#include + +#include "anbox/cli.h" + +namespace anbox { +namespace cmds { +class WaitReady : public cli::CommandWithFlagsAndAction { + public: + WaitReady(); + + private: + bool use_system_dbus_ = false; +}; +} // namespace cmds +} // namespace anbox + +#endif diff --git a/src/anbox/container/management_api_skeleton.cpp b/src/anbox/container/management_api_skeleton.cpp index bc2fa43..3374414 100644 --- a/src/anbox/container/management_api_skeleton.cpp +++ b/src/anbox/container/management_api_skeleton.cpp @@ -37,6 +37,8 @@ ManagementApiSkeleton::~ManagementApiSkeleton() {} void ManagementApiSkeleton::start_container( anbox::protobuf::container::StartContainer const *request, anbox::protobuf::rpc::Void *response, google::protobuf::Closure *done) { + DEBUG(""); + if (container_->state() == Container::State::running) { response->set_error("Container is already running"); done->Run(); diff --git a/src/anbox/daemon.cpp b/src/anbox/daemon.cpp index 806503d..9d294ef 100644 --- a/src/anbox/daemon.cpp +++ b/src/anbox/daemon.cpp @@ -27,6 +27,7 @@ #include "anbox/cmds/system_info.h" #include "anbox/cmds/launch.h" #include "anbox/cmds/version.h" +#include "anbox/cmds/wait_ready.h" #include @@ -40,8 +41,8 @@ Daemon::Daemon() .command(std::make_shared()) .command(std::make_shared()) .command(std::make_shared()) - .command(std::make_shared()); - + .command(std::make_shared()) + .command(std::make_shared()); Log().Init(anbox::Logger::Severity::kWarning); diff --git a/src/anbox/dbus/bus.cpp b/src/anbox/dbus/bus.cpp new file mode 100644 index 0000000..9c3af18 --- /dev/null +++ b/src/anbox/dbus/bus.cpp @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2016 Simon Fels + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + * + */ + +#include "anbox/dbus/bus.h" +#include "anbox/logger.h" + +namespace anbox { +namespace dbus { +Bus::Bus(Type type) : + type_{type} { + + int ret = 0; + switch (type) { + case Type::Session: + ret = sd_bus_open_user(&bus_); + break; + case Type::System: + ret = sd_bus_open_system(&bus_); + break; + default: + throw std::invalid_argument("Invalid bus type"); + } + + if (ret < 0 || !bus_) + throw std::runtime_error("Failed to connect to DBus"); +} + +Bus::~Bus() { + stop(); + + if (bus_) + sd_bus_unref(bus_); +} + +bool Bus::has_service_with_name(const std::string &name) { + auto r = sd_bus_get_name_creds(bus_, + name.c_str(), + 0, + nullptr); + return r >= 0; +} + +sd_bus* Bus::raw() { + return bus_; +} + +void Bus::run_async() { + running_ = true; + worker_thread_ = std::thread(&Bus::worker_main, this); +} + +void Bus::stop() { + running_ = false; + if (worker_thread_.joinable()) + worker_thread_.join(); +} + +void Bus::worker_main() { + while (running_) { + auto ret = sd_bus_process(bus_, nullptr); + if (ret < 0) + break; + if (ret > 0) + continue; + + ret = sd_bus_wait(bus_, 1000 * 500); + if (ret < 0) + break; + } +} +} // namespace dbus +} // namespace anbox diff --git a/src/anbox/dbus/bus.h b/src/anbox/dbus/bus.h new file mode 100644 index 0000000..218e472 --- /dev/null +++ b/src/anbox/dbus/bus.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2016 Simon Fels + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + * + */ + +#ifndef ANBOX_DBUS_BUS_H_ +#define ANBOX_DBUS_BUS_H_ + +#include "anbox/do_not_copy_or_move.h" + +#include +#include +#include +#include + +#include + +namespace anbox { +namespace dbus { +class Bus : public DoNotCopyOrMove { + public: + enum class Type { + System, + Session + }; + + Bus(Type type); + ~Bus(); + + sd_bus* raw(); + + bool has_service_with_name(const std::string& name); + void run_async(); + void stop(); + + private: + void worker_main(); + + Type type_; + sd_bus *bus_ = nullptr; + std::thread worker_thread_; + std::atomic_bool running_{false}; +}; +using BusPtr = std::shared_ptr; +} // namespace dbus +} // namespace anbox + +#endif diff --git a/src/anbox/dbus/interface.h b/src/anbox/dbus/interface.h index 9335acc..abce44f 100644 --- a/src/anbox/dbus/interface.h +++ b/src/anbox/dbus/interface.h @@ -18,51 +18,28 @@ #ifndef ANBOX_DBUS_INTERFACE_H_ #define ANBOX_DBUS_INTERFACE_H_ -#include -#include - -#include -#include - namespace anbox { namespace dbus { namespace interface { struct Service { - static inline std::string name() { return "org.anbox"; } - static inline std::string path() { return "/"; } + static inline const char* name() { return "org.anbox"; } + static inline const char* path() { return "/org/anbox"; } }; struct ApplicationManager { - static inline std::string name() { return "org.anbox.ApplicationManager"; } + static inline const char* name() { return "org.anbox.ApplicationManager"; } struct Methods { - struct Launch { - static inline std::string name() { return "Launch"; } - typedef anbox::dbus::interface::ApplicationManager Interface; - typedef void ResultType; - static inline std::chrono::milliseconds default_timeout() { - return std::chrono::seconds{60}; - } - }; + struct Launch { + static inline const char* name() { return "Launch"; } + }; }; struct Properties { - DBUS_CPP_READABLE_PROPERTY_DEF(Ready, ApplicationManager, bool) + struct Ready { + static inline const char* name() { return "Ready"; } + }; }; }; } // namespace interface } // namespace dbus } // namespace anbox -namespace core { -namespace dbus { -namespace traits { -template <> -struct Service { - static inline const std::string& interface_name() { - static const std::string s{"org.anbox.ApplicationManager"}; - return s; - } -}; -} // namespace traits -} // namespace dbus -} // namespace core - #endif diff --git a/src/anbox/dbus/sd_bus_helpers.h b/src/anbox/dbus/sd_bus_helpers.h new file mode 100644 index 0000000..f52d5a2 --- /dev/null +++ b/src/anbox/dbus/sd_bus_helpers.h @@ -0,0 +1,121 @@ +/* +* Copyright (C) 2018 OpenBMC +* +* Taken from https://github.com/openbmc/sdbusplus/blob/master/sdbusplus/vtable.hpp +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#ifndef ANBOX_DBUS_SD_BUS_HELPERS_H_ +#define ANBOX_DBUS_SD_BUS_HELPERS_H_ + +#include + +namespace anbox { +namespace dbus { +namespace sdbus { +namespace vtable { +constexpr sd_bus_vtable start(uint64_t flags) +{ + sd_bus_vtable v{}; + v.type = _SD_BUS_VTABLE_START; + v.flags = flags; + v.x.start = decltype(v.x.start){sizeof(sd_bus_vtable)}; + + return v; +} + +constexpr sd_bus_vtable end() { + sd_bus_vtable v{}; + v.type = _SD_BUS_VTABLE_END; + + return v; +} + +constexpr sd_bus_vtable method_o(const char* member, const char* signature, + const char* result, + sd_bus_message_handler_t handler, size_t offset, + uint64_t flags) { + sd_bus_vtable v{}; + v.type = _SD_BUS_VTABLE_METHOD; + v.flags = flags; + v.x.method = decltype(v.x.method){member, signature, result, handler, offset}; + + return v; +} + +constexpr sd_bus_vtable method(const char* member, const char* signature, + const char* result, sd_bus_message_handler_t handler, + uint64_t flags) { + return method_o(member, signature, result, handler, 0, flags); +} + +constexpr sd_bus_vtable signal(const char* member, const char* signature, uint64_t flags) { + sd_bus_vtable v{}; + v.type = _SD_BUS_VTABLE_SIGNAL; + v.flags = flags; + v.x.signal = decltype(v.x.signal){member, signature}; + + return v; +} + +constexpr sd_bus_vtable property(const char* member, const char* signature, + sd_bus_property_get_t get, + uint64_t flags) { + sd_bus_vtable v{}; + v.type = _SD_BUS_VTABLE_PROPERTY; + v.flags = flags; + v.x.property = decltype(v.x.property){member, signature, get, nullptr, 0}; + + return v; +} + +constexpr sd_bus_vtable property(const char* member, const char* signature, + sd_bus_property_get_t get, + sd_bus_property_set_t set, + uint64_t flags) { + sd_bus_vtable v{}; + v.type = _SD_BUS_VTABLE_WRITABLE_PROPERTY; + v.flags = flags; + v.x.property = decltype(v.x.property){member, signature, get, set, 0}; + + return v; +} + +constexpr sd_bus_vtable property_o(const char* member, const char* signature, + size_t offset, uint64_t flags) { + sd_bus_vtable v{}; + v.type = _SD_BUS_VTABLE_PROPERTY; + v.flags = flags; + v.x.property = decltype(v.x.property){member, signature, nullptr, nullptr, offset}; + + return v; +} + +constexpr sd_bus_vtable property_o(const char* member, const char* signature, + sd_bus_property_set_t set, size_t offset, + uint64_t flags) +{ + sd_bus_vtable v{}; + v.type = _SD_BUS_VTABLE_WRITABLE_PROPERTY; + v.flags = flags; + v.x.property = decltype(v.x.property){member, signature, nullptr, set, offset}; + + return v; +} +} // namespace vtable +} // namespace sd_bus +} // namespace dbus +} // namespace anbox + +#endif diff --git a/src/anbox/dbus/skeleton/application_manager.cpp b/src/anbox/dbus/skeleton/application_manager.cpp index e021a2d..44cf71e 100644 --- a/src/anbox/dbus/skeleton/application_manager.cpp +++ b/src/anbox/dbus/skeleton/application_manager.cpp @@ -16,85 +16,164 @@ */ #include "anbox/dbus/skeleton/application_manager.h" -#include "anbox/android/intent.h" #include "anbox/dbus/interface.h" -#include "anbox/dbus/codecs.h" +#include "anbox/dbus/sd_bus_helpers.h" +#include "anbox/android/intent.h" #include "anbox/logger.h" +#include + #include +namespace { +int parse_string_from_message(sd_bus_message *m, std::string &str) { + const char *contents = nullptr; + auto r = sd_bus_message_enter_container(m, SD_BUS_TYPE_VARIANT, contents); + if (r < 0) + return r; + + const char *value; + r = sd_bus_message_read(m, "s", &value); + if (r < 0) + return r; + + str = value; + + r = sd_bus_message_exit_container(m); + if (r < 0) + return r; + + return 0; +} +} // namespace + namespace anbox { namespace dbus { namespace skeleton { -ApplicationManager::ApplicationManager( - const core::dbus::Bus::Ptr &bus, const core::dbus::Object::Ptr &object, - const std::shared_ptr &impl) - : bus_(bus), object_(object), impl_(impl), - properties_{ object_->get_property() }, - signals_{ object_->get_signal() } { +const sd_bus_vtable ApplicationManager::vtable[] = { + sdbus::vtable::start(0), + sdbus::vtable::method("Launch", "a{sv}s", "", ApplicationManager::method_launch, SD_BUS_VTABLE_UNPRIVILEGED), + sdbus::vtable::property("Ready", "b", ApplicationManager::property_ready_get, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + sdbus::vtable::end() +}; - object_->install_method_handler( - [this](const core::dbus::Message::Ptr &msg) { - auto reader = msg->reader(); +int ApplicationManager::method_launch(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { + auto r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "{sv}"); + if (r < 0) + return r; - android::Intent intent; - reader >> intent.action; - reader >> intent.uri; - reader >> intent.type; - reader >> intent.flags; - reader >> intent.package; - reader >> intent.component; + android::Intent intent; - std::int32_t left, top, right, bottom; - reader >> left; - reader >> top; - reader >> right; - reader >> bottom; - graphics::Rect launch_bounds{left, top, right, bottom}; + while ((r = sd_bus_message_enter_container(m, SD_BUS_TYPE_DICT_ENTRY, "sv")) > 0) { + const char *key = nullptr; - wm::Stack::Id stack = wm::Stack::Id::Default; - reader >> stack; + r = sd_bus_message_read(m, "s", &key); + if (r < 0) + return r; - core::dbus::Message::Ptr reply; + if (strcmp(key, "package") == 0) { + r = parse_string_from_message(m, intent.package); + if (r < 0) + return r; + } else if (strcmp(key, "component") == 0) { + r = parse_string_from_message(m, intent.component); + if (r < 0) + return r; + } else if (strcmp(key, "action") == 0) { + r = parse_string_from_message(m, intent.action); + if (r < 0) + return r; + } else if (strcmp(key, "type") == 0) { + r = parse_string_from_message(m, intent.type); + if (r < 0) + return r; + } else if (strcmp(key, "uri") == 0) { + r = parse_string_from_message(m, intent.uri); + if (r < 0) + return r; + } + } - try { - launch(intent, launch_bounds, stack); - reply = core::dbus::Message::make_method_return(msg); - } catch (std::exception const &err) { - reply = core::dbus::Message::make_error(msg, "org.anbox.Error.Failed", - err.what()); - } + r = sd_bus_message_exit_container(m); + if (r < 0) + return r; - bus_->send(reply); - }); + const char *stack = nullptr; + r = sd_bus_message_read(m, "s", &stack); + if (r < 0) + return r; + + wm::Stack::Id launch_stack = wm::Stack::Id::Default; + if (stack && strlen(stack) > 0) { + auto s = std::string(stack); + std::istringstream i(s); + i >> launch_stack; + } + + if (intent.package.length() == 0) { + sd_bus_error_set_const(ret_error, "org.anbox.InvalidArgument", "No package specified"); + return -EINVAL; + } + + auto thiz = static_cast(userdata); + try { + thiz->launch(intent, graphics::Rect::Invalid, launch_stack); + } catch (std::exception &err) { + ERROR("Failed to launch application: %s", err.what()); + sd_bus_error_set_const(ret_error, "org.anbox.InternalError", err.what()); + return -EIO; + } + + return sd_bus_reply_method_return(m, ""); +} + +int ApplicationManager::property_ready_get(sd_bus *bus, const char *path, const char *interface, + const char *property, sd_bus_message *reply, + void *userdata, sd_bus_error *ret_error) { + + (void) bus; + (void) path; + (void) interface; + (void) property; + (void) ret_error; + + auto thiz = static_cast(userdata); + + return sd_bus_message_append(reply, "b", thiz->impl_->ready().get()); +} + +ApplicationManager::ApplicationManager(const BusPtr& bus, const std::shared_ptr &impl) + : bus_(bus), impl_(impl) { + + const auto r = sd_bus_add_object_vtable(bus_->raw(), + &obj_slot_, + interface::Service::path(), + interface::ApplicationManager::name(), + vtable, + this); + if (r < 0) + std::runtime_error("Failed to setup application manager DBus service"); - // Forward AndroidApi status to our dbus property - properties_.ready->install([&]() { return impl_->ready().get(); }); impl_->ready().changed().connect([&](bool value) { - properties_.ready->set(value); - on_property_value_changed(value); + (void) value; + + sd_bus_emit_properties_changed(bus_->raw(), + interface::Service::path(), + interface::ApplicationManager::name(), + interface::ApplicationManager::Properties::Ready::name(), + nullptr); }); } ApplicationManager::~ApplicationManager() {} -template -void ApplicationManager::on_property_value_changed(const typename Property::ValueType& value) -{ - typedef std::map Dictionary; - - static const std::vector the_empty_list_of_invalidated_properties; - - Dictionary dict; dict[Property::name()] = core::dbus::types::Variant::encode(value); - - signals_.properties_changed->emit( - std::make_tuple(core::dbus::traits::Service::interface_name(), - dict, the_empty_list_of_invalidated_properties)); -} - void ApplicationManager::launch(const android::Intent &intent, const graphics::Rect &launch_bounds, const wm::Stack::Id &stack) { + if (!impl_->ready()) + throw std::runtime_error("Anbox not yet ready to launch applications"); + + DEBUG("Launching %s", intent); impl_->launch(intent, launch_bounds, stack); } diff --git a/src/anbox/dbus/skeleton/application_manager.h b/src/anbox/dbus/skeleton/application_manager.h index 8900815..68d3cee 100644 --- a/src/anbox/dbus/skeleton/application_manager.h +++ b/src/anbox/dbus/skeleton/application_manager.h @@ -19,22 +19,16 @@ #define ANBOX_DBUS_SKELETON_APPLICATION_MANAGER_H_ #include "anbox/application/manager.h" +#include "anbox/dbus/bus.h" -#include -#include -#include -#include - -#include "anbox/dbus/interface.h" +#include namespace anbox { namespace dbus { namespace skeleton { class ApplicationManager : public anbox::application::Manager { public: - ApplicationManager(const core::dbus::Bus::Ptr &bus, - const core::dbus::Object::Ptr &object, - const std::shared_ptr &impl); + ApplicationManager(const BusPtr& bus, const std::shared_ptr &impl); ~ApplicationManager(); void launch(const android::Intent &intent, @@ -44,20 +38,15 @@ class ApplicationManager : public anbox::application::Manager { core::Property& ready() override; private: - template - void on_property_value_changed(const typename Property::ValueType& value); + static const sd_bus_vtable vtable[]; + static int method_launch(sd_bus_message *m, void *userdata, sd_bus_error *ret_error); + static int property_ready_get(sd_bus *bus, const char *path, const char *interface, + const char *property, sd_bus_message *reply, void *userdata, + sd_bus_error *ret_error); - core::dbus::Bus::Ptr bus_; - core::dbus::Service::Ptr service_; - core::dbus::Object::Ptr object_; + BusPtr bus_; std::shared_ptr impl_; - struct { - std::shared_ptr> ready; - } properties_; - struct { - core::dbus::Signal::Ptr properties_changed; - } signals_; + sd_bus_slot *obj_slot_ = nullptr; }; } // namespace skeleton } // namespace dbus diff --git a/src/anbox/dbus/skeleton/service.cpp b/src/anbox/dbus/skeleton/service.cpp index 8f309c2..9d62d2d 100644 --- a/src/anbox/dbus/skeleton/service.cpp +++ b/src/anbox/dbus/skeleton/service.cpp @@ -15,29 +15,33 @@ * */ -#include "anbox/dbus/skeleton/service.h" #include "anbox/dbus/interface.h" +#include "anbox/dbus/skeleton/service.h" #include "anbox/dbus/skeleton/application_manager.h" +#include "anbox/logger.h" namespace anbox { namespace dbus { namespace skeleton { -std::shared_ptr Service::create_for_bus( - const core::dbus::Bus::Ptr &bus, - const std::shared_ptr &application_manager) { - auto service = core::dbus::Service::add_service(bus, anbox::dbus::interface::Service::name()); - auto object = service->add_object_for_path(anbox::dbus::interface::Service::path()); - return std::make_shared(bus, service, object, application_manager); +std::shared_ptr Service::create_for_bus(const BusPtr& bus, const std::shared_ptr &impl) { + return std::shared_ptr(new Service(bus, impl)); } -Service::Service( - const core::dbus::Bus::Ptr &bus, const core::dbus::Service::Ptr &service, - const core::dbus::Object::Ptr &object, - const std::shared_ptr &application_manager) - : bus_(bus), - service_(service), - object_(object), - application_manager_(std::make_shared(bus_, object_, application_manager)) {} +Service::Service(const BusPtr& bus, const std::shared_ptr &impl) + : bus_{bus} { + if (!bus_) + throw std::invalid_argument("Missing bus object"); + + const auto r = sd_bus_request_name(bus_->raw(), + interface::Service::name(), + 0); + if (r < 0) + throw std::runtime_error("Failed to request DBus service name"); + + DEBUG("Successfully acquired DBus service name"); + + application_manager_ = std::make_shared(bus, impl); +} Service::~Service() {} } // namespace skeleton diff --git a/src/anbox/dbus/skeleton/service.h b/src/anbox/dbus/skeleton/service.h index 46fe8ee..4532a1b 100644 --- a/src/anbox/dbus/skeleton/service.h +++ b/src/anbox/dbus/skeleton/service.h @@ -19,11 +19,14 @@ #define ANBOX_DBUS_SKELETON_SERVICE_H_ #include "anbox/application/manager.h" +#include "anbox/dbus/bus.h" #include "anbox/do_not_copy_or_move.h" -#include -#include -#include +#include +#include +#include + +#include namespace anbox { namespace dbus { @@ -31,20 +34,14 @@ namespace skeleton { class ApplicationManager; class Service : public DoNotCopyOrMove { public: - static std::shared_ptr create_for_bus( - const core::dbus::Bus::Ptr &bus, - const std::shared_ptr &application_manager); + static std::shared_ptr create_for_bus(const BusPtr& bus, const std::shared_ptr &impl); - Service( - const core::dbus::Bus::Ptr &bus, const core::dbus::Service::Ptr &service, - const core::dbus::Object::Ptr &object, - const std::shared_ptr &application_manager); ~Service(); private: - core::dbus::Bus::Ptr bus_; - core::dbus::Service::Ptr service_; - core::dbus::Object::Ptr object_; + Service(const BusPtr& bus, const std::shared_ptr &impl); + + BusPtr bus_; std::shared_ptr application_manager_; }; } // namespace skeleton diff --git a/src/anbox/dbus/stub/application_manager.cpp b/src/anbox/dbus/stub/application_manager.cpp index 743d73e..6d75425 100644 --- a/src/anbox/dbus/stub/application_manager.cpp +++ b/src/anbox/dbus/stub/application_manager.cpp @@ -15,44 +15,108 @@ * */ -#include "anbox/dbus/stub/application_manager.h" #include "anbox/dbus/interface.h" -#include "anbox/dbus/codecs.h" +#include "anbox/dbus/stub/application_manager.h" #include "anbox/logger.h" +#include + namespace anbox { namespace dbus { namespace stub { -std::shared_ptr ApplicationManager::create_for_bus(const core::dbus::Bus::Ptr &bus) { - auto service = core::dbus::Service::use_service_or_throw_if_not_available(bus, anbox::dbus::interface::Service::name()); - auto object = service->object_for_path(anbox::dbus::interface::Service::path()); - return std::make_shared(bus, service, object); +std::shared_ptr ApplicationManager::create_for_bus(const BusPtr& bus) { + return std::shared_ptr(new ApplicationManager(bus)); } -ApplicationManager::ApplicationManager(const core::dbus::Bus::Ptr &bus, - const core::dbus::Service::Ptr &service, - const core::dbus::Object::Ptr &object) - : bus_(bus), service_(service), object_(object), - properties_{ object_->get_property() } { +ApplicationManager::ApplicationManager(const BusPtr& bus) + : bus_(bus) { - // Forward changes on the dbus property to our users - ready_.install([&]() { return properties_.ready->get(); }); - properties_.ready->changed().connect([&](bool value) { ready_.set(value); }); + if (!bus_->has_service_with_name(interface::Service::name())) + throw std::runtime_error("Application manager service is not running yet"); + + update_properties(); } ApplicationManager::~ApplicationManager() {} +void ApplicationManager::update_properties() { + bool ready = false; + const auto r = sd_bus_get_property_trivial(bus_->raw(), + interface::Service::name(), + interface::Service::path(), + interface::ApplicationManager::name(), + interface::ApplicationManager::Properties::Ready::name(), + nullptr, + 'b', + &ready); + if (r < 0) + throw std::runtime_error("Failed to retrieve ready property from application manager"); + + ready_.set(ready); +} + void ApplicationManager::launch(const android::Intent &intent, const graphics::Rect &launch_bounds, const wm::Stack::Id &stack) { - auto result = object_->invoke_method_synchronously< - anbox::dbus::interface::ApplicationManager::Methods::Launch, - anbox::dbus::interface::ApplicationManager::Methods::Launch::ResultType>( - intent.action, intent.uri, intent.type, intent.flags, intent.package, - intent.component, launch_bounds.left(), launch_bounds.top(), - launch_bounds.right(), launch_bounds.bottom(), stack); + (void) launch_bounds; - if (result.is_error()) throw std::runtime_error(result.error().print()); + sd_bus_message *m = nullptr; + auto r = sd_bus_message_new_method_call(bus_->raw(), + &m, + interface::Service::name(), + interface::Service::path(), + interface::ApplicationManager::name(), + interface::ApplicationManager::Methods::Launch::name()); + if (r < 0) + throw std::runtime_error("Failed to construct DBus message"); + + r = sd_bus_message_open_container(m, 'a', "{sv}"); + if (r < 0) + throw std::runtime_error("Failed to construct DBus message"); + + if (intent.package.length() > 0) { + r = sd_bus_message_append(m, "{sv}", "package", "s", intent.package.c_str()); + if (r < 0) + throw std::runtime_error("Failed to construct DBus message"); + } + + if (intent.component.length() > 0) { + r = sd_bus_message_append(m, "{sv}", "component", "s", intent.component.c_str()); + if (r < 0) + throw std::runtime_error("Failed to construct DBus message"); + } + + if (intent.action.length() > 0) { + r = sd_bus_message_append(m, "{sv}", "action", "s", intent.action.c_str()); + if (r < 0) + throw std::runtime_error("Failed to construct DBus message"); + } + + if (intent.type.length() > 0) { + r = sd_bus_message_append(m, "{sv}", "type", "s", intent.type.c_str()); + if (r < 0) + throw std::runtime_error("Failed to construct DBus message"); + } + + if (intent.uri.length() > 0) { + r = sd_bus_message_append(m, "{sv}", "uri", "s", intent.uri.c_str()); + if (r < 0) + throw std::runtime_error("Failed to construct DBus message"); + } + + r = sd_bus_message_close_container(m); + if (r < 0) + throw std::runtime_error("Failed to construct DBus message"); + + std::ostringstream launch_stack; + launch_stack << stack; + r = sd_bus_message_append(m, "s", launch_stack.str().c_str()); + if (r < 0) + throw std::runtime_error("Failed to construct DBus message"); + + r = sd_bus_call(bus_->raw(), m, 0, nullptr, nullptr); + if (r < 0) + throw std::runtime_error("Failed to call Anbox application manager"); } core::Property& ApplicationManager::ready() { diff --git a/src/anbox/dbus/stub/application_manager.h b/src/anbox/dbus/stub/application_manager.h index f70a93b..2c8b736 100644 --- a/src/anbox/dbus/stub/application_manager.h +++ b/src/anbox/dbus/stub/application_manager.h @@ -19,24 +19,17 @@ #define ANBOX_DBUS_SKELETON_APPLICATION_MANAGER_H_ #include "anbox/application/manager.h" +#include "anbox/dbus/bus.h" -#include -#include -#include - -#include "anbox/dbus/interface.h" +#include namespace anbox { namespace dbus { namespace stub { class ApplicationManager : public anbox::application::Manager { public: - static std::shared_ptr create_for_bus( - const core::dbus::Bus::Ptr &bus); + static std::shared_ptr create_for_bus(const BusPtr& bus); - ApplicationManager(const core::dbus::Bus::Ptr &bus, - const core::dbus::Service::Ptr &service, - const core::dbus::Object::Ptr &object); ~ApplicationManager(); void launch(const android::Intent &intent, @@ -45,14 +38,13 @@ class ApplicationManager : public anbox::application::Manager { core::Property& ready() override; + void update_properties(); + private: - core::dbus::Bus::Ptr bus_; - core::dbus::Service::Ptr service_; - core::dbus::Object::Ptr object_; + ApplicationManager(const BusPtr& bus); + + BusPtr bus_; core::Property ready_; - struct { - std::shared_ptr> ready; - } properties_; }; } // namespace stub } // namespace dbus diff --git a/src/anbox/platform/sdl/audio_sink.cpp b/src/anbox/platform/sdl/audio_sink.cpp index d9e04d2..5a0c4ad 100644 --- a/src/anbox/platform/sdl/audio_sink.cpp +++ b/src/anbox/platform/sdl/audio_sink.cpp @@ -49,7 +49,7 @@ bool AudioSink::connect_audio() { spec_.freq = 44100; spec_.format = AUDIO_S16; spec_.channels = 2; - spec_.samples = 4096; + spec_.samples = 1024; spec_.callback = &AudioSink::on_data_requested; spec_.userdata = this;