| /* |
| * Copyright 2018 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 "common/camera_mojo_channel_manager_impl.h" |
| |
| #include <grp.h> |
| |
| #include <string> |
| #include <utility> |
| |
| #include <base/bind.h> |
| #include <base/check.h> |
| #include <base/files/file_util.h> |
| #include <base/logging.h> |
| #include <base/no_destructor.h> |
| #include <mojo/core/embedder/embedder.h> |
| |
| #include "cros-camera/common.h" |
| #include "cros-camera/constants.h" |
| #include "cros-camera/ipc_util.h" |
| |
| namespace cros { |
| |
| namespace { |
| |
| constexpr ino_t kInvalidInodeNum = 0; |
| |
| // Gets the socket file by |socket_path| and checks if it is in correct group |
| // and has correct permission. Returns |kInvalidInodeNum| if it is invalid. |
| // Otherwise, returns its inode number. |
| ino_t GetSocketInodeNumber(const base::FilePath& socket_path) { |
| // Ensure that socket file is ready before trying to connect the dispatcher. |
| struct group arc_camera_group; |
| struct group* result = nullptr; |
| char buf[1024]; |
| |
| getgrnam_r(constants::kArcCameraGroup, &arc_camera_group, buf, sizeof(buf), |
| &result); |
| if (!result) { |
| return kInvalidInodeNum; |
| } |
| |
| int mode; |
| if (!base::GetPosixFilePermissions(socket_path, &mode) || mode != 0660) { |
| return kInvalidInodeNum; |
| } |
| |
| struct stat st; |
| if (stat(socket_path.value().c_str(), &st) || |
| st.st_gid != arc_camera_group.gr_gid) { |
| return kInvalidInodeNum; |
| } |
| return st.st_ino; |
| } |
| |
| base::UnguessableToken ReadServerToken() { |
| static constexpr char kServerTokenPath[] = "/run/camera_tokens/server/token"; |
| |
| base::FilePath token_path(kServerTokenPath); |
| std::string token_string; |
| if (!base::ReadFileToString(token_path, &token_string)) { |
| LOGF(ERROR) << "Failed to read server token"; |
| return {}; |
| } |
| return cros::TokenFromString(token_string); |
| } |
| |
| } // namespace |
| |
| // static |
| CameraMojoChannelManagerImpl* CameraMojoChannelManagerImpl::instance_ = nullptr; |
| |
| CameraMojoChannelManagerImpl::CameraMojoChannelManagerImpl() |
| : ipc_thread_("MojoIpcThread"), bound_socket_inode_num_(kInvalidInodeNum) { |
| instance_ = this; |
| if (!ipc_thread_.StartWithOptions( |
| base::Thread::Options(base::MessagePumpType::IO, 0))) { |
| LOGF(ERROR) << "Failed to start IPC Thread"; |
| return; |
| } |
| mojo::core::Init(); |
| ipc_support_ = std::make_unique<mojo::core::ScopedIPCSupport>( |
| ipc_thread_.task_runner(), |
| mojo::core::ScopedIPCSupport::ShutdownPolicy::FAST); |
| |
| base::FilePath socket_path(constants::kCrosCameraSocketPathString); |
| if (!watcher_.Watch( |
| socket_path, base::FilePathWatcher::Type::kNonRecursive, |
| base::Bind(&CameraMojoChannelManagerImpl::OnSocketFileStatusChange, |
| base::Unretained(this)))) { |
| LOGF(ERROR) << "Failed to watch socket path"; |
| return; |
| } |
| } |
| |
| CameraMojoChannelManagerImpl::~CameraMojoChannelManagerImpl() { |
| if (ipc_thread_.IsRunning()) { |
| ipc_thread_.task_runner()->PostTask( |
| FROM_HERE, |
| base::Bind(&CameraMojoChannelManagerImpl::TearDownMojoEnvOnIpcThread, |
| base::Unretained(this))); |
| ipc_thread_.Stop(); |
| } |
| } |
| |
| // static |
| CameraMojoChannelManagerToken* CameraMojoChannelManagerToken::CreateInstance() { |
| return new CameraMojoChannelManagerImpl(); |
| } |
| |
| // static |
| CameraMojoChannelManager* CameraMojoChannelManager::GetInstance() { |
| DCHECK(CameraMojoChannelManagerImpl::instance_ != nullptr); |
| return CameraMojoChannelManagerImpl::instance_; |
| } |
| |
| scoped_refptr<base::SingleThreadTaskRunner> |
| CameraMojoChannelManagerImpl::GetIpcTaskRunner() { |
| CHECK(ipc_thread_.task_runner()); |
| return ipc_thread_.task_runner(); |
| } |
| |
| void CameraMojoChannelManagerImpl::RegisterServer( |
| mojo::PendingRemote<mojom::CameraHalServer> server, |
| mojom::CameraHalDispatcher::RegisterServerWithTokenCallback |
| on_construct_callback, |
| Callback on_error_callback) { |
| DCHECK(ipc_thread_.task_runner()->BelongsToCurrentThread()); |
| VLOGF_ENTER(); |
| |
| camera_hal_server_task_ = { |
| .pendingReceiverOrRemote = std::move(server), |
| .on_construct_callback = std::move(on_construct_callback), |
| .on_error_callback = std::move(on_error_callback)}; |
| ipc_thread_.task_runner()->PostTask( |
| FROM_HERE, |
| base::Bind(&CameraMojoChannelManagerImpl::TryConnectToDispatcher, |
| base::Unretained(this))); |
| } |
| |
| void CameraMojoChannelManagerImpl::CreateMjpegDecodeAccelerator( |
| mojo::PendingReceiver<mojom::MjpegDecodeAccelerator> receiver, |
| Callback on_construct_callback, |
| Callback on_error_callback) { |
| DCHECK(ipc_thread_.task_runner()->BelongsToCurrentThread()); |
| VLOGF_ENTER(); |
| |
| JpegPendingMojoTask<mojo::PendingReceiver<mojom::MjpegDecodeAccelerator>> |
| pending_task = {.pendingReceiverOrRemote = std::move(receiver), |
| .on_construct_callback = std::move(on_construct_callback), |
| .on_error_callback = std::move(on_error_callback)}; |
| jda_tasks_.push_back(std::move(pending_task)); |
| ipc_thread_.task_runner()->PostTask( |
| FROM_HERE, |
| base::Bind(&CameraMojoChannelManagerImpl::TryConnectToDispatcher, |
| base::Unretained(this))); |
| } |
| |
| void CameraMojoChannelManagerImpl::CreateJpegEncodeAccelerator( |
| mojo::PendingReceiver<mojom::JpegEncodeAccelerator> receiver, |
| Callback on_construct_callback, |
| Callback on_error_callback) { |
| DCHECK(ipc_thread_.task_runner()->BelongsToCurrentThread()); |
| VLOGF_ENTER(); |
| |
| JpegPendingMojoTask<mojo::PendingReceiver<mojom::JpegEncodeAccelerator>> |
| pending_task = {.pendingReceiverOrRemote = std::move(receiver), |
| .on_construct_callback = std::move(on_construct_callback), |
| .on_error_callback = std::move(on_error_callback)}; |
| jea_tasks_.push_back(std::move(pending_task)); |
| ipc_thread_.task_runner()->PostTask( |
| FROM_HERE, |
| base::Bind(&CameraMojoChannelManagerImpl::TryConnectToDispatcher, |
| base::Unretained(this))); |
| } |
| |
| mojo::Remote<mojom::CameraAlgorithmOps> |
| CameraMojoChannelManagerImpl::CreateCameraAlgorithmOpsRemote( |
| const std::string& socket_path, const std::string& pipe_name) { |
| VLOGF_ENTER(); |
| |
| mojo::ScopedMessagePipeHandle parent_pipe; |
| mojo::Remote<mojom::CameraAlgorithmOps> algorithm_ops; |
| |
| base::FilePath socket_file_path(socket_path); |
| MojoResult result = cros::CreateMojoChannelToChildByUnixDomainSocket( |
| socket_file_path, &parent_pipe, pipe_name); |
| if (result != MOJO_RESULT_OK) { |
| LOGF(WARNING) << "Failed to create Mojo Channel to " |
| << socket_file_path.value(); |
| return mojo::Remote<mojom::CameraAlgorithmOps>(); |
| } |
| |
| algorithm_ops.Bind(mojo::PendingRemote<mojom::CameraAlgorithmOps>( |
| std::move(parent_pipe), 0u)); |
| |
| LOGF(INFO) << "Connected to CameraAlgorithmOps"; |
| |
| VLOGF_EXIT(); |
| return algorithm_ops; |
| } |
| |
| void CameraMojoChannelManagerImpl::OnSocketFileStatusChange( |
| const base::FilePath& socket_path, bool error) { |
| if (error) { |
| LOGF(ERROR) << "Error occurs in socket file watcher."; |
| return; |
| } |
| |
| ipc_thread_.task_runner()->PostTask( |
| FROM_HERE, |
| base::Bind( |
| &CameraMojoChannelManagerImpl::OnSocketFileStatusChangeOnIpcThread, |
| base::Unretained(this))); |
| } |
| |
| void CameraMojoChannelManagerImpl::OnSocketFileStatusChangeOnIpcThread() { |
| DCHECK(ipc_thread_.task_runner()->BelongsToCurrentThread()); |
| VLOGF_ENTER(); |
| |
| base::FilePath socket_path(constants::kCrosCameraSocketPathString); |
| if (dispatcher_.is_bound()) { |
| // If the dispatcher is already bound but the inode number of the socket is |
| // unreadable or has been changed, we assume the other side of the |
| // dispatcher (Chrome) might be destroyed. As a result, we fire the on error |
| // event here in case it is not fired correctly. |
| if (bound_socket_inode_num_ != GetSocketInodeNumber(socket_path)) { |
| ipc_thread_.task_runner()->PostTask( |
| FROM_HERE, |
| base::Bind(&CameraMojoChannelManagerImpl::ResetDispatcherPtr, |
| base::Unretained(this))); |
| } |
| return; |
| } |
| |
| ipc_thread_.task_runner()->PostTask( |
| FROM_HERE, |
| base::Bind(&CameraMojoChannelManagerImpl::TryConnectToDispatcher, |
| base::Unretained(this))); |
| } |
| |
| void CameraMojoChannelManagerImpl::TryConnectToDispatcher() { |
| DCHECK(ipc_thread_.task_runner()->BelongsToCurrentThread()); |
| VLOGF_ENTER(); |
| |
| if (dispatcher_.is_bound()) { |
| TryConsumePendingMojoTasks(); |
| return; |
| } |
| |
| base::FilePath socket_path(constants::kCrosCameraSocketPathString); |
| ino_t socket_inode_num = GetSocketInodeNumber(socket_path); |
| if (socket_inode_num == kInvalidInodeNum) { |
| return; |
| } |
| |
| mojo::ScopedMessagePipeHandle child_pipe; |
| MojoResult result = cros::CreateMojoChannelToParentByUnixDomainSocket( |
| socket_path, &child_pipe); |
| if (result != MOJO_RESULT_OK) { |
| LOGF(WARNING) << "Failed to create Mojo Channel to " << socket_path.value(); |
| return; |
| } |
| |
| dispatcher_ = mojo::Remote<cros::mojom::CameraHalDispatcher>( |
| mojo::PendingRemote<cros::mojom::CameraHalDispatcher>( |
| std::move(child_pipe), 0u)); |
| dispatcher_.set_disconnect_handler( |
| base::Bind(&CameraMojoChannelManagerImpl::ResetDispatcherPtr, |
| base::Unretained(this))); |
| bound_socket_inode_num_ = socket_inode_num; |
| |
| TryConsumePendingMojoTasks(); |
| } |
| |
| void CameraMojoChannelManagerImpl::TryConsumePendingMojoTasks() { |
| DCHECK(ipc_thread_.task_runner()->BelongsToCurrentThread()); |
| VLOGF_ENTER(); |
| |
| if (camera_hal_server_task_.pendingReceiverOrRemote) { |
| auto server_token = ReadServerToken(); |
| if (server_token.is_empty()) { |
| LOGF(ERROR) << "Failed to read server token"; |
| std::move(camera_hal_server_task_.on_construct_callback) |
| .Run(-EPERM, mojo::NullRemote()); |
| } else { |
| auto token = mojo_base::mojom::UnguessableToken::New(); |
| token->high = server_token.GetHighForSerialization(); |
| token->low = server_token.GetLowForSerialization(); |
| dispatcher_->RegisterServerWithToken( |
| std::move(camera_hal_server_task_.pendingReceiverOrRemote), |
| std::move(token), |
| std::move(camera_hal_server_task_.on_construct_callback)); |
| } |
| } |
| |
| for (auto& task : jda_tasks_) { |
| if (task.pendingReceiverOrRemote) { |
| dispatcher_->GetMjpegDecodeAccelerator( |
| std::move(task.pendingReceiverOrRemote)); |
| std::move(task.on_construct_callback).Run(); |
| } |
| } |
| |
| for (auto& task : jea_tasks_) { |
| if (task.pendingReceiverOrRemote) { |
| dispatcher_->GetJpegEncodeAccelerator( |
| std::move(task.pendingReceiverOrRemote)); |
| std::move(task.on_construct_callback).Run(); |
| } |
| } |
| } |
| |
| void CameraMojoChannelManagerImpl::TearDownMojoEnvOnIpcThread() { |
| DCHECK(ipc_thread_.task_runner()->BelongsToCurrentThread()); |
| VLOGF_ENTER(); |
| |
| ResetDispatcherPtr(); |
| ipc_support_.reset(); |
| } |
| |
| void CameraMojoChannelManagerImpl::ResetDispatcherPtr() { |
| DCHECK(ipc_thread_.task_runner()->BelongsToCurrentThread()); |
| VLOGF_ENTER(); |
| |
| if (camera_hal_server_task_.on_error_callback) { |
| std::move(camera_hal_server_task_.on_error_callback).Run(); |
| camera_hal_server_task_ = {}; |
| } |
| |
| for (auto& task : jda_tasks_) { |
| if (task.on_error_callback) { |
| std::move(task.on_error_callback).Run(); |
| } |
| } |
| jda_tasks_.clear(); |
| |
| for (auto& task : jea_tasks_) { |
| if (task.on_error_callback) { |
| std::move(task.on_error_callback).Run(); |
| } |
| } |
| jea_tasks_.clear(); |
| |
| dispatcher_.reset(); |
| bound_socket_inode_num_ = kInvalidInodeNum; |
| } |
| |
| } // namespace cros |