| // Copyright 2019 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <memory> |
| #include <utility> |
| |
| #include <base/guid.h> |
| #include <base/optional.h> |
| #include <base/time/time.h> |
| #include <brillo/dbus/dbus_proxy_util.h> |
| #include <chromeos/dbus/service_constants.h> |
| #include <dbus/bus.h> |
| #include <dbus/exported_object.h> |
| #include <dbus/message.h> |
| #include <dbus/object_proxy.h> |
| #include <dbus/scoped_dbus_error.h> |
| |
| #include <vm_plugin_dispatcher/proto_bindings/vm_plugin_dispatcher.pb.h> |
| |
| #include "vm_tools/concierge/vmplugin_dispatcher_interface.h" |
| |
| namespace vm_tools { |
| namespace concierge { |
| namespace pvm { |
| namespace dispatcher { |
| namespace { |
| |
| constexpr char kVmpluginImageDir[] = "/run/pvm-images"; |
| |
| constexpr base::TimeDelta kVmShutdownTimeout = base::TimeDelta::FromMinutes(2); |
| constexpr base::TimeDelta kVmSuspendTimeout = base::TimeDelta::FromSeconds(20); |
| |
| // Native Parallels error codes. |
| constexpr int PRL_ERR_SUCCESS = 0; |
| constexpr int PRL_ERR_DISP_SHUTDOWN_IN_PROCESS = 0x80000404; |
| constexpr int PRL_ERR_LICENSE_NOT_VALID = 0x80011000; |
| constexpr int PRL_ERR_LICENSE_EXPIRED = 0x80011001; |
| constexpr int PRL_ERR_LICENSE_WRONG_VERSION = 0x80011002; |
| constexpr int PRL_ERR_LICENSE_WRONG_PLATFORM = 0x80011004; |
| constexpr int PRL_ERR_LICENSE_BETA_KEY_RELEASE_PRODUCT = 0x80011011; |
| constexpr int PRL_ERR_LICENSE_RELEASE_KEY_BETA_PRODUCT = 0x80011013; |
| constexpr int PRL_ERR_LICENSE_SUBSCR_EXPIRED = 0x80011074; |
| constexpr int PRL_ERR_JLIC_WRONG_HWID = 0x80057005; |
| constexpr int PRL_ERR_JLIC_LICENSE_DISABLED = 0x80057010; |
| constexpr int PRL_ERR_JLIC_WEB_PORTAL_ACCESS_REQUIRED = 0x80057012; |
| |
| VmOpResult ConvertNativeResult(int result) { |
| switch (result) { |
| case PRL_ERR_SUCCESS: |
| return VmOpResult::SUCCESS; |
| case PRL_ERR_DISP_SHUTDOWN_IN_PROCESS: |
| return VmOpResult::DISPATCHER_SHUTTING_DOWN; |
| case PRL_ERR_LICENSE_NOT_VALID: |
| case PRL_ERR_LICENSE_EXPIRED: |
| case PRL_ERR_LICENSE_WRONG_VERSION: |
| case PRL_ERR_LICENSE_WRONG_PLATFORM: |
| case PRL_ERR_LICENSE_BETA_KEY_RELEASE_PRODUCT: |
| case PRL_ERR_LICENSE_RELEASE_KEY_BETA_PRODUCT: |
| case PRL_ERR_LICENSE_SUBSCR_EXPIRED: |
| case PRL_ERR_JLIC_WRONG_HWID: |
| case PRL_ERR_JLIC_LICENSE_DISABLED: |
| case PRL_ERR_JLIC_WEB_PORTAL_ACCESS_REQUIRED: |
| return VmOpResult::DISPATCHER_LICENSE_ERROR; |
| default: |
| return VmOpResult::DISPATCHER_GENERIC_ERROR; |
| } |
| } |
| |
| VmOpResult ConvertDispatcherResult(plugin_dispatcher::VmErrorCode result, |
| int native_result) { |
| switch (result) { |
| case plugin_dispatcher::VM_SUCCESS: |
| return VmOpResult::SUCCESS; |
| case plugin_dispatcher::VM_ERR_NATIVE_RESULT_CODE: |
| return ConvertNativeResult(native_result); |
| default: |
| return VmOpResult::INTERNAL_ERROR; |
| } |
| } |
| |
| bool GetVmInfo(scoped_refptr<dbus::Bus> bus, |
| dbus::ObjectProxy* proxy, |
| const VmId& vm_id, |
| base::Optional<vm_tools::plugin_dispatcher::VmInfo>* info) { |
| dbus::MethodCall method_call( |
| vm_tools::plugin_dispatcher::kVmPluginDispatcherInterface, |
| vm_tools::plugin_dispatcher::kListVmsMethod); |
| dbus::MessageWriter writer(&method_call); |
| |
| vm_tools::plugin_dispatcher::ListVmRequest request; |
| |
| request.set_owner_id(vm_id.owner_id()); |
| request.set_vm_name_uuid(vm_id.name()); |
| |
| if (!writer.AppendProtoAsArrayOfBytes(request)) { |
| LOG(ERROR) << "Failed to encode ListVmRequest protobuf"; |
| return false; |
| } |
| |
| std::unique_ptr<dbus::Response> dbus_response = |
| brillo::dbus_utils::CallDBusMethod( |
| bus, proxy, &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT); |
| if (!dbus_response) { |
| LOG(ERROR) << "Failed to send ListVm message to dispatcher service"; |
| return false; |
| } |
| |
| dbus::MessageReader reader(dbus_response.get()); |
| vm_tools::plugin_dispatcher::ListVmResponse response; |
| if (!reader.PopArrayOfBytesAsProto(&response)) { |
| LOG(ERROR) << "Failed to parse ListVmResponse protobuf"; |
| return false; |
| } |
| |
| if (response.error() != vm_tools::plugin_dispatcher::VM_SUCCESS) { |
| LOG(ERROR) << "Failed to get VM info: " << std::hex << std::showbase |
| << response.error() << " (" << response.result_code() << ")"; |
| return false; |
| } |
| |
| *info = base::nullopt; |
| for (const auto& vm_info : response.vm_info()) { |
| if (vm_info.name() == vm_id.name()) { |
| *info = vm_info; |
| break; |
| } |
| } |
| |
| return info; |
| } |
| |
| } // namespace |
| |
| dbus::ObjectProxy* GetServiceProxy(scoped_refptr<dbus::Bus> bus) { |
| return bus->GetObjectProxy( |
| vm_tools::plugin_dispatcher::kVmPluginDispatcherServiceName, |
| dbus::ObjectPath( |
| vm_tools::plugin_dispatcher::kVmPluginDispatcherServicePath)); |
| } |
| |
| bool RegisterVm(scoped_refptr<dbus::Bus> bus, |
| dbus::ObjectProxy* proxy, |
| const VmId& vm_id, |
| const base::FilePath& image_path) { |
| dbus::MethodCall method_call( |
| vm_tools::plugin_dispatcher::kVmPluginDispatcherInterface, |
| vm_tools::plugin_dispatcher::kRegisterVmMethod); |
| dbus::MessageWriter writer(&method_call); |
| |
| vm_tools::plugin_dispatcher::RegisterVmRequest request; |
| |
| request.set_owner_id(vm_id.owner_id()); |
| request.set_new_name(vm_id.name()); |
| base::FilePath dispatcher_image_path(base::FilePath(kVmpluginImageDir) |
| .Append(vm_id.owner_id()) |
| .Append(image_path.BaseName())); |
| LOG(INFO) << "Registering VM at " << dispatcher_image_path.value(); |
| request.set_path(dispatcher_image_path.value()); |
| // We do not track VMs by uuid but rather by their name, so always generate |
| // new one. |
| request.set_new_uuid(base::GenerateGUID()); |
| request.set_preserve_uuid(false); |
| request.set_regenerate_src_uuid(true); |
| |
| if (!writer.AppendProtoAsArrayOfBytes(request)) { |
| LOG(ERROR) << "Failed to encode RegisterVmRequest protobuf"; |
| return false; |
| } |
| |
| std::unique_ptr<dbus::Response> dbus_response = |
| brillo::dbus_utils::CallDBusMethod( |
| bus, proxy, &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT); |
| if (!dbus_response) { |
| LOG(ERROR) << "Failed to send RegisterVm message to dispatcher service"; |
| return false; |
| } |
| |
| dbus::MessageReader reader(dbus_response.get()); |
| vm_tools::plugin_dispatcher::RegisterVmResponse response; |
| if (!reader.PopArrayOfBytesAsProto(&response)) { |
| LOG(ERROR) << "Failed to parse RegisterVmResponse protobuf"; |
| return false; |
| } |
| |
| if (response.error() != vm_tools::plugin_dispatcher::VM_SUCCESS) { |
| LOG(ERROR) << "Failed to register VM: " << std::hex << std::showbase |
| << response.error() << " (" << response.result_code() << ")"; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool UnregisterVm(scoped_refptr<dbus::Bus> bus, |
| dbus::ObjectProxy* proxy, |
| const VmId& vm_id) { |
| LOG(INFO) << "Unregistering VM " << vm_id; |
| |
| dbus::MethodCall method_call( |
| vm_tools::plugin_dispatcher::kVmPluginDispatcherInterface, |
| vm_tools::plugin_dispatcher::kUnregisterVmMethod); |
| dbus::MessageWriter writer(&method_call); |
| |
| vm_tools::plugin_dispatcher::UnregisterVmRequest request; |
| |
| request.set_owner_id(vm_id.owner_id()); |
| request.set_vm_name_uuid(vm_id.name()); |
| |
| if (!writer.AppendProtoAsArrayOfBytes(request)) { |
| LOG(ERROR) << "Failed to encode UnregisterVmRequest protobuf"; |
| return false; |
| } |
| |
| std::unique_ptr<dbus::Response> dbus_response = |
| brillo::dbus_utils::CallDBusMethod( |
| bus, proxy, &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT); |
| if (!dbus_response) { |
| LOG(ERROR) << "Failed to send UnregisterVm message to dispatcher service"; |
| return false; |
| } |
| |
| dbus::MessageReader reader(dbus_response.get()); |
| vm_tools::plugin_dispatcher::UnregisterVmResponse response; |
| if (!reader.PopArrayOfBytesAsProto(&response)) { |
| LOG(ERROR) << "Failed to parse UnregisterVmResponse protobuf"; |
| return false; |
| } |
| |
| if (response.error() != vm_tools::plugin_dispatcher::VM_SUCCESS) { |
| LOG(ERROR) << "Failed to unregister VM: " << std::hex << std::showbase |
| << response.error() << " (" << response.result_code() << ")"; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool IsVmRegistered(scoped_refptr<dbus::Bus> bus, |
| dbus::ObjectProxy* proxy, |
| const VmId& vm_id, |
| bool* result) { |
| LOG(INFO) << "Checking whether VM " << vm_id << " is registered"; |
| |
| base::Optional<vm_tools::plugin_dispatcher::VmInfo> info; |
| if (!GetVmInfo(bus, proxy, vm_id, &info)) |
| return false; |
| |
| *result = info.has_value(); |
| return true; |
| } |
| |
| bool IsVmShutDown(scoped_refptr<dbus::Bus> bus, |
| dbus::ObjectProxy* proxy, |
| const VmId& vm_id, |
| bool* result) { |
| LOG(INFO) << "Checking whether VM " << vm_id << " is shut down"; |
| |
| base::Optional<vm_tools::plugin_dispatcher::VmInfo> info; |
| if (!GetVmInfo(bus, proxy, vm_id, &info)) |
| return false; |
| |
| *result = |
| info.has_value() && |
| info.value().state() == vm_tools::plugin_dispatcher::VM_STATE_STOPPED; |
| return true; |
| } |
| |
| VmOpResult ShutdownVm(scoped_refptr<dbus::Bus> bus, |
| dbus::ObjectProxy* proxy, |
| const VmId& vm_id) { |
| LOG(INFO) << "Shutting down VM " << vm_id; |
| |
| dbus::MethodCall method_call( |
| vm_tools::plugin_dispatcher::kVmPluginDispatcherInterface, |
| vm_tools::plugin_dispatcher::kStopVmMethod); |
| dbus::MessageWriter writer(&method_call); |
| |
| vm_tools::plugin_dispatcher::StopVmRequest request; |
| |
| request.set_owner_id(vm_id.owner_id()); |
| request.set_vm_name_uuid(vm_id.name()); |
| // Allow request to fail if VM is busy. |
| request.set_noforce(true); |
| |
| if (!writer.AppendProtoAsArrayOfBytes(request)) { |
| LOG(ERROR) << "Failed to encode StopVmRequest protobuf"; |
| return VmOpResult::INTERNAL_ERROR; |
| } |
| |
| dbus::ScopedDBusError dbus_error; |
| std::unique_ptr<dbus::Response> dbus_response = |
| brillo::dbus_utils::CallDBusMethodWithErrorResponse( |
| bus, proxy, &method_call, kVmShutdownTimeout.InMilliseconds(), |
| &dbus_error); |
| if (!dbus_response) { |
| if (dbus_error.is_set() && |
| strcmp(dbus_error.name(), DBUS_ERROR_SERVICE_UNKNOWN) == 0) { |
| LOG(ERROR) << "Failed to send ShutdownVm request to dispatcher: service " |
| "unavailable"; |
| return VmOpResult::DISPATCHER_NOT_AVAILABLE; |
| } else if (dbus_error.is_set() && |
| strcmp(dbus_error.name(), DBUS_ERROR_NO_REPLY) == 0) { |
| LOG(ERROR) << "ShutdownVm request to dispatcher timed out"; |
| return VmOpResult::DISPATCHER_TIMEOUT; |
| } else { |
| LOG(ERROR) << "Failed to send ShutdownVm message to dispatcher service"; |
| return VmOpResult::INTERNAL_ERROR; |
| } |
| } |
| |
| dbus::MessageReader reader(dbus_response.get()); |
| vm_tools::plugin_dispatcher::StopVmResponse response; |
| if (!reader.PopArrayOfBytesAsProto(&response)) { |
| LOG(ERROR) << "Failed to parse StopVmResponse protobuf"; |
| return VmOpResult::INTERNAL_ERROR; |
| } |
| |
| return ConvertDispatcherResult(response.error(), response.result_code()); |
| } |
| |
| VmOpResult SuspendVm(scoped_refptr<dbus::Bus> bus, |
| dbus::ObjectProxy* proxy, |
| const VmId& vm_id) { |
| LOG(INFO) << "Suspending VM " << vm_id; |
| |
| dbus::MethodCall method_call( |
| vm_tools::plugin_dispatcher::kVmPluginDispatcherInterface, |
| vm_tools::plugin_dispatcher::kSuspendVmMethod); |
| dbus::MessageWriter writer(&method_call); |
| |
| vm_tools::plugin_dispatcher::SuspendVmRequest request; |
| |
| request.set_owner_id(vm_id.owner_id()); |
| request.set_vm_name_uuid(vm_id.name()); |
| |
| if (!writer.AppendProtoAsArrayOfBytes(request)) { |
| LOG(ERROR) << "Failed to encode SuspendVmRequest protobuf"; |
| return VmOpResult::INTERNAL_ERROR; |
| } |
| |
| dbus::ScopedDBusError dbus_error; |
| std::unique_ptr<dbus::Response> dbus_response = |
| brillo::dbus_utils::CallDBusMethodWithErrorResponse( |
| bus, proxy, &method_call, kVmSuspendTimeout.InMilliseconds(), |
| &dbus_error); |
| if (!dbus_response) { |
| if (dbus_error.is_set() && |
| strcmp(dbus_error.name(), DBUS_ERROR_SERVICE_UNKNOWN) == 0) { |
| return VmOpResult::DISPATCHER_NOT_AVAILABLE; |
| } else if (dbus_error.is_set() && |
| strcmp(dbus_error.name(), DBUS_ERROR_NO_REPLY) == 0) { |
| return VmOpResult::DISPATCHER_TIMEOUT; |
| } else { |
| LOG(ERROR) << "Failed to send SuspendVm message to dispatcher service"; |
| return VmOpResult::INTERNAL_ERROR; |
| } |
| } |
| |
| dbus::MessageReader reader(dbus_response.get()); |
| vm_tools::plugin_dispatcher::SuspendVmResponse response; |
| if (!reader.PopArrayOfBytesAsProto(&response)) { |
| LOG(ERROR) << "Failed to parse SuspendVmResponse protobuf"; |
| return VmOpResult::INTERNAL_ERROR; |
| } |
| |
| return ConvertDispatcherResult(response.error(), response.result_code()); |
| } |
| |
| void RegisterVmToolsChangedCallbacks( |
| dbus::ObjectProxy* proxy, |
| dbus::ObjectProxy::SignalCallback cb, |
| dbus::ObjectProxy::OnConnectedCallback on_connected_cb) { |
| proxy->ConnectToSignal( |
| vm_tools::plugin_dispatcher::kVmPluginDispatcherServiceName, |
| vm_tools::plugin_dispatcher::kVmToolsStateChangedSignal, cb, |
| std::move(on_connected_cb)); |
| } |
| |
| bool ParseVmToolsChangedSignal(dbus::Signal* signal, |
| std::string* owner_id, |
| std::string* vm_name, |
| bool* running) { |
| DCHECK_EQ(signal->GetInterface(), |
| vm_tools::plugin_dispatcher::kVmPluginDispatcherInterface); |
| DCHECK_EQ(signal->GetMember(), |
| vm_tools::plugin_dispatcher::kVmToolsStateChangedSignal); |
| |
| vm_tools::plugin_dispatcher::VmToolsStateChangedSignal message; |
| dbus::MessageReader reader(signal); |
| if (!reader.PopArrayOfBytesAsProto(&message)) { |
| LOG(ERROR) << "Failed to parse VmToolsStateChangedSignal from DBus Signal"; |
| return false; |
| } |
| |
| auto state = message.vm_tools_state(); |
| LOG(INFO) << "Tools raw state: " << state; |
| |
| *owner_id = message.owner_id(); |
| *vm_name = message.vm_name(); |
| *running = state == vm_tools::plugin_dispatcher::VM_TOOLS_STATE_INSTALLED; |
| return true; |
| } |
| |
| } // namespace dispatcher |
| } // namespace pvm |
| } // namespace concierge |
| } // namespace vm_tools |