1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
|
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromeos/ash/components/mojo_service_manager/connection.h"
#include <errno.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <cstdlib>
#include <string>
#include <utility>
#include "base/check_op.h"
#include "base/compiler_specific.h"
#include "base/functional/bind.h"
#include "base/metrics/histogram_functions.h"
#include "base/no_destructor.h"
#include "base/posix/eintr_wrapper.h"
#include "base/threading/platform_thread.h"
#include "base/time/time.h"
#include "base/timer/elapsed_timer.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/platform/platform_channel.h"
#include "mojo/public/cpp/system/invitation.h"
namespace ash::mojo_service_manager {
namespace mojom = chromeos::mojo_service_manager::mojom;
namespace {
// The socket path to connect to the service manager.
constexpr char kServiceManagerSocketPath[] = "/run/mojo/service_manager.sock";
// The default mojo pipe name for bootstrap the mojo.
constexpr int kDefaultMojoInvitationPipeName = 0;
// The connection may fail if the socket is not exist or the permission is not
// set to the right permission. This could happen if the ChromeOS mojo service
// manager is starting. We may need to wait for a while and retry.
//
// TODO(b/234318452): Clean up this retry logic after we collect enough UMA
// data.
//
// The retry interval of connecting to the service manager. It is expected that
// normally the first retry should be able to perform the bootstrap on all
// devices.
constexpr base::TimeDelta kRetryInterval = base::Milliseconds(1);
// The retry timeout of connecting to the service manager.
constexpr base::TimeDelta kRetryTimeout = base::Seconds(5);
base::ScopedFD ConnectToServiceManagerUnixSocket() {
base::ScopedFD sock{socket(AF_UNIX, SOCK_STREAM, 0)};
if (!sock.is_valid()) {
PLOG(ERROR) << "Failed to create socket.";
return base::ScopedFD{};
}
struct sockaddr_un unix_addr {
.sun_family = AF_UNIX,
};
static_assert(sizeof(kServiceManagerSocketPath) <=
sizeof(unix_addr.sun_path));
UNSAFE_TODO(strncpy(unix_addr.sun_path, kServiceManagerSocketPath,
sizeof(kServiceManagerSocketPath)));
int rc = HANDLE_EINTR(connect(sock.get(),
reinterpret_cast<const sockaddr*>(&unix_addr),
sizeof(unix_addr)));
if (rc == -1 && errno != EISCONN) {
PLOG(ERROR) << "Failed to connect to service manager unix socket.";
return base::ScopedFD{};
}
return sock;
}
mojo::PendingRemote<mojom::ServiceManager> ConnectToMojoServiceManager() {
base::ScopedFD sock = ConnectToServiceManagerUnixSocket();
if (!sock.is_valid())
return mojo::PendingRemote<mojom::ServiceManager>{};
auto invitation = mojo::IncomingInvitation::Accept(
mojo::PlatformChannelEndpoint(mojo::PlatformHandle(std::move(sock))));
mojo::ScopedMessagePipeHandle pipe =
invitation.ExtractMessagePipe(kDefaultMojoInvitationPipeName);
return mojo::PendingRemote<mojom::ServiceManager>(std::move(pipe), 0u);
}
mojo::Remote<mojom::ServiceManager>& GetRemote() {
static base::NoDestructor<mojo::Remote<mojom::ServiceManager>> instance;
return *instance;
}
// Sends the histogram to record the retry times of bootstrap.
void SendBootsrapRetryTimesHistogram(int retry_times) {
// Note that sample will be 0 if didn't retry, and it will be in the underflow
// bucket.
base::UmaHistogramCustomCounts("Ash.MojoServiceManager.BootstrapRetryTimes",
retry_times, 1, 5000, 50);
}
// Sends the histogram to record whether the connection is lost during ash
// running.
void SendIsConnectionLostHistogram(bool is_connection_lost) {
base::UmaHistogramBoolean("Ash.MojoServiceManager.IsConnectionLost",
is_connection_lost);
}
void OnDisconnect(uint32_t reason, const std::string& message) {
SendIsConnectionLostHistogram(true);
LOG(FATAL) << "Disconnecting from ChromeOS mojo service manager is "
"unexpected. Reason: "
<< reason << ", message: " << message;
}
} // namespace
bool BootstrapServiceManagerConnection() {
CHECK(!GetRemote().is_bound()) << "remote_ has already bound.";
// We block and sleep here because we assume that it doesn't need to retry at
// all.
int retry_times = 0;
for (base::ElapsedTimer timer; timer.Elapsed() < kRetryTimeout;
base::PlatformThread::Sleep(kRetryInterval), ++retry_times) {
mojo::PendingRemote<mojom::ServiceManager> remote =
ConnectToMojoServiceManager();
if (!remote.is_valid())
continue;
SendBootsrapRetryTimesHistogram(retry_times);
GetRemote().Bind(std::move(remote));
GetRemote().set_disconnect_with_reason_handler(
base::BindOnce(&OnDisconnect));
return true;
}
SendBootsrapRetryTimesHistogram(retry_times);
return false;
}
bool IsServiceManagerBound() {
return GetRemote().is_bound();
}
void ResetServiceManagerConnection() {
SendIsConnectionLostHistogram(false);
GetRemote().reset();
}
mojom::ServiceManagerProxy* GetServiceManagerProxy() {
return GetRemote().get();
}
void SetServiceManagerRemoteForTesting( // IN-TEST
mojo::PendingRemote<mojom::ServiceManager> remote) {
CHECK(remote.is_valid());
GetRemote().Bind(std::move(remote));
GetRemote().set_disconnect_with_reason_handler(base::BindOnce(&OnDisconnect));
}
mojo::PendingReceiver<chromeos::mojo_service_manager::mojom::ServiceManager>
BootstrapServiceManagerInUtilityProcess() {
return GetRemote().BindNewPipeAndPassReceiver();
}
} // namespace ash::mojo_service_manager
|