blob: 60103523365b3bafe82f9e0416634c61b656a686 [file] [log] [blame]
// Copyright 2020 The Chromium 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 "chrome/updater/prefs_impl.h"
#include <mach/mach.h>
#include <servers/bootstrap.h>
#include "base/logging.h"
#include "base/mac/mach_logging.h"
#include "base/mac/scoped_mach_port.h"
#include "base/threading/platform_thread.h"
#include "base/time/time.h"
#include "chrome/updater/updater_branding.h"
namespace {
// Mach service name for the global prefs lock.
constexpr char kPrefsLockMachServiceName[] =
MAC_BUNDLE_IDENTIFIER_STRING ".lock";
// Interval to poll for lock availability if it is not immediately available.
// Final interval will be truncated to fit the available timeout.
constexpr base::TimeDelta kPrefsLockPollingInterval =
base::TimeDelta::FromSeconds(3);
//
// Attempts to acquire the receive right to a named Mach service.
// Single attempt, no retries. Logs errors other than "permission denied",
// since "permission denied" typically means the service receive rights have
// already been assigned.
//
// Returns the receive right if the right was successfully acquired. If the
// right cannot be acquired for any reason, returns an invalid right instead.
base::mac::ScopedMachReceiveRight TryAcquireReceive(const char* service_name) {
base::mac::ScopedMachReceiveRight target_right;
kern_return_t check_in_result = bootstrap_check_in(
bootstrap_port, service_name,
base::mac::ScopedMachReceiveRight::Receiver(target_right).get());
if (check_in_result != KERN_SUCCESS) {
// Log error reports for all errors other than BOOTSTRAP_NOT_PRIVILEGED.
// BOOTSTRAP_NOT_PRIVILEGED is not logged because it just means that another
// process has acquired the receive rights for this service.
if (check_in_result != BOOTSTRAP_NOT_PRIVILEGED) {
BOOTSTRAP_LOG(ERROR, check_in_result)
<< "bootstrap_check_in to acquire prefs lock: "
<< kPrefsLockMachServiceName;
} else {
BOOTSTRAP_VLOG(2, check_in_result)
<< "Prefs lock already held: " << kPrefsLockMachServiceName;
}
return base::mac::ScopedMachReceiveRight();
}
return target_right;
}
// Sleep until the lock should be retried, up to an approximate maximum of
// max_wait (within the tolerances of timing, scheduling, etc.).
void WaitToRetryLock(base::TimeDelta max_wait) {
// This is a polling implementation of Mach service locking.
// TODO(1135787): replace with a non-polling Mach notification approach.
const base::TimeDelta wait_time = max_wait < kPrefsLockPollingInterval
? max_wait
: kPrefsLockPollingInterval;
base::PlatformThread::Sleep(wait_time);
}
} // anonymous namespace
namespace updater {
class ScopedPrefsLockImpl {
public:
// Constructs a ScopedPrefsLockImpl from a receive right.
explicit ScopedPrefsLockImpl(base::mac::ScopedMachReceiveRight receive_right);
// Releases the receive right (and therefore releases the lock).
~ScopedPrefsLockImpl() = default;
ScopedPrefsLockImpl(const ScopedPrefsLockImpl&) = delete;
ScopedPrefsLockImpl& operator=(const ScopedPrefsLockImpl&) = delete;
private:
// The Mach port representing the held lock itself. We only care about
// service ownership; no messages are transfered with this port.
base::mac::ScopedMachReceiveRight receive_right_;
};
ScopedPrefsLockImpl::ScopedPrefsLockImpl(
base::mac::ScopedMachReceiveRight receive_right)
: receive_right_(std::move(receive_right)) {
mach_port_type_t port_type = 0;
kern_return_t port_check_result =
mach_port_type(mach_task_self(), receive_right_.get(), &port_type);
MACH_CHECK(port_check_result == KERN_SUCCESS, port_check_result)
<< "ScopedPrefsLockImpl could not verify lock port";
CHECK(port_type & MACH_PORT_TYPE_RECEIVE)
<< "ScopedPrefsLockImpl given port without receive right";
}
ScopedPrefsLock::ScopedPrefsLock(std::unique_ptr<ScopedPrefsLockImpl> impl)
: impl_(std::move(impl)) {}
ScopedPrefsLock::~ScopedPrefsLock() = default;
std::unique_ptr<ScopedPrefsLock> AcquireGlobalPrefsLock(
base::TimeDelta timeout) {
// First, try to acquire the lock. If the timeout is zero or negative,
// this is the only attempt we will make.
base::mac::ScopedMachReceiveRight receive_right(
TryAcquireReceive(kPrefsLockMachServiceName));
// Set up time limits.
base::TimeTicks deadline = base::TimeTicks::Now() + timeout;
constexpr base::TimeDelta kDeltaZero;
// Loop until we acquire the lock or time out. Note that we have already
// tried once to acquire the lock, so this loop will execute zero times
// if we have already succeeded (or if the timeout was zero). Therefore,
// this loop waits for retry *before* attempting to acquire the lock;
// the special case of the first try was handled outside the loop.
for (base::TimeDelta remain = deadline - base::TimeTicks::Now();
!receive_right.is_valid() && remain > kDeltaZero;
remain = deadline - base::TimeTicks::Now()) {
WaitToRetryLock(remain);
receive_right = TryAcquireReceive(kPrefsLockMachServiceName);
}
if (!receive_right.is_valid()) {
return nullptr;
}
return std::make_unique<ScopedPrefsLock>(
std::make_unique<ScopedPrefsLockImpl>(std::move(receive_right)));
}
} // namespace updater