| // Copyright 2020 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 "ocr/daemon.h" |
| |
| #include <memory> |
| #include <string> |
| #include <sysexits.h> |
| #include <utility> |
| |
| #include <base/bind.h> |
| #include <base/callback.h> |
| #include <base/check.h> |
| #include <base/files/file_util.h> |
| #include <base/logging.h> |
| #include <base/threading/thread_task_runner_handle.h> |
| #include <base/unguessable_token.h> |
| #include <dbus/object_path.h> |
| #include <chromeos/dbus/service_constants.h> |
| #include <mojo/public/cpp/platform/platform_channel_endpoint.h> |
| #include <mojo/public/cpp/system/invitation.h> |
| #include <mojo/core/embedder/embedder.h> |
| |
| #include "ocr/ocr_service_impl.h" |
| |
| namespace ocr { |
| |
| OcrDaemon::OcrDaemon() : brillo::DBusServiceDaemon(kOcrServiceName) { |
| ocr_service_impl_ = std::make_unique<OcrServiceImpl>(); |
| ocr_service_impl_->SetOnDisconnectCallback(base::BindRepeating( |
| &OcrDaemon::OnDisconnect, weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| OcrDaemon::~OcrDaemon() = default; |
| |
| int OcrDaemon::OnInit() { |
| int return_code = brillo::DBusServiceDaemon::OnInit(); |
| if (return_code != EX_OK) |
| return return_code; |
| |
| // Initialize Mojo IPC. |
| mojo::core::Init(); |
| ipc_support_ = std::make_unique<mojo::core::ScopedIPCSupport>( |
| base::ThreadTaskRunnerHandle::Get() /* io_thread_task_runner */, |
| mojo::core::ScopedIPCSupport::ShutdownPolicy:: |
| CLEAN /* blocking shutdown */); |
| |
| return EX_OK; |
| } |
| |
| void OcrDaemon::RegisterDBusObjectsAsync( |
| brillo::dbus_utils::AsyncEventSequencer* sequencer) { |
| DCHECK(!dbus_object_); |
| dbus_object_ = std::make_unique<brillo::dbus_utils::DBusObject>( |
| nullptr /* object_manager */, bus_, dbus::ObjectPath(kOcrServicePath)); |
| brillo::dbus_utils::DBusInterface* dbus_interface = |
| dbus_object_->AddOrGetInterface(kOcrServiceInterface); |
| DCHECK(dbus_interface); |
| dbus_interface->AddSimpleMethodHandler(kBootstrapMojoConnectionMethod, |
| base::Unretained(this), |
| &OcrDaemon::BootstrapMojoConnection); |
| dbus_object_->RegisterAsync(sequencer->GetHandler( |
| "Failed to register D-Bus object" /* descriptive_message */, |
| true /* failure_is_fatal */)); |
| } |
| |
| std::string OcrDaemon::BootstrapMojoConnection(const base::ScopedFD& mojo_fd, |
| bool should_accept_invitation) { |
| VLOG(1) << "Received BootstrapMojoConnection D-Bus request"; |
| |
| if (!mojo_fd.is_valid()) { |
| constexpr char kInvalidFileDescriptorError[] = |
| "ScopedFD extracted from D-Bus call was invalid (i.e. empty)"; |
| LOG(ERROR) << kInvalidFileDescriptorError; |
| return kInvalidFileDescriptorError; |
| } |
| |
| // We need a file descriptor that stays alive after the current method |
| // finishes, but libbrillo's D-Bus wrappers currently don't support passing |
| // base::ScopedFD by value. |
| base::ScopedFD mojo_fd_copy(HANDLE_EINTR(dup(mojo_fd.get()))); |
| if (!mojo_fd_copy.is_valid()) { |
| constexpr char kFailedDuplicationError[] = |
| "Failed to duplicate the Mojo file descriptor"; |
| PLOG(ERROR) << kFailedDuplicationError; |
| return kFailedDuplicationError; |
| } |
| |
| if (!base::SetCloseOnExec(mojo_fd_copy.get())) { |
| constexpr char kFailedSettingFdCloexec[] = |
| "Failed to set FD_CLOEXEC on Mojo file descriptor"; |
| PLOG(ERROR) << kFailedSettingFdCloexec; |
| return kFailedSettingFdCloexec; |
| } |
| |
| std::string token; |
| mojo::ScopedMessagePipeHandle mojo_message_pipe; |
| if (should_accept_invitation) { |
| if (mojo_service_bind_attempted_) { |
| // This should not normally be triggered, since the other endpoint - the |
| // browser process - should bootstrap the Mojo connection only once, and |
| // when that process is killed the Mojo shutdown notification should have |
| // been received earlier. But handle this case to be on the safe side. |
| // After we restart, the browser process is expected to invoke the |
| // bootstrapping again. |
| LOG(ERROR) << "Shutting down due to repeated Mojo bootstrap requests"; |
| ocr_service_impl_.reset(); |
| Quit(); |
| return ""; |
| } |
| |
| // Connect to Mojo in the requesting process. |
| mojo::IncomingInvitation invitation = |
| mojo::IncomingInvitation::Accept(mojo::PlatformChannelEndpoint( |
| mojo::PlatformHandle(std::move(mojo_fd_copy)))); |
| mojo_message_pipe = |
| invitation.ExtractMessagePipe(kBootstrapMojoConnectionChannelToken); |
| mojo_service_bind_attempted_ = true; |
| } else { |
| // Create a unique token which will allow the requesting process to connect |
| // to us via Mojo. |
| mojo::OutgoingInvitation invitation; |
| token = base::UnguessableToken::Create().ToString(); |
| mojo_message_pipe = invitation.AttachMessagePipe(token); |
| mojo::OutgoingInvitation::Send( |
| std::move(invitation), base::kNullProcessHandle, |
| mojo::PlatformChannelEndpoint( |
| mojo::PlatformHandle(std::move(mojo_fd_copy)))); |
| } |
| ocr_service_impl_->AddReceiver( |
| mojo::PendingReceiver< |
| chromeos::ocr::mojom::OpticalCharacterRecognitionService>( |
| std::move(mojo_message_pipe)), |
| should_accept_invitation); |
| VLOG(1) << "Successfully bootstrapped Mojo connection"; |
| return token; |
| } |
| |
| void OcrDaemon::OnDisconnect(bool should_quit) { |
| if (should_quit) { |
| LOG(ERROR) << "OcrDaemon lost Mojo connection to the browser; quitting."; |
| ocr_service_impl_.reset(); |
| Quit(); |
| } |
| } |
| |
| } // namespace ocr |