blob: 5309eb33316e00f8c728a0b2ce45154238e611a0 [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 "base/mac/rosetta.h"
#include <AppKit/AppKit.h>
#include <CoreFoundation/CoreFoundation.h>
#include <dlfcn.h>
#include <mach/machine.h>
#include <string.h>
#include <sys/sysctl.h>
#include <sys/types.h>
#include <utility>
#include "base/check.h"
#include "base/files/file_path.h"
#include "base/mac/scoped_nsobject.h"
#include "base/mac/sdk_forward_declarations.h"
#include "base/no_destructor.h"
#include "base/strings/sys_string_conversions.h"
#include "base/threading/scoped_blocking_call.h"
#include "build/build_config.h"
namespace base {
namespace mac {
#if defined(ARCH_CPU_X86_64)
// https://developer.apple.com/documentation/apple_silicon/about_the_rosetta_translation_environment#3616845
bool ProcessIsTranslated() {
int ret = 0;
size_t size = sizeof(ret);
if (sysctlbyname("sysctl.proc_translated", &ret, &size, nullptr, 0) == -1)
return false;
return ret;
}
#endif // ARCH_CPU_X86_64
#if defined(ARCH_CPU_ARM64)
bool IsRosettaInstalled() {
if (@available(macOS 11.0, *)) {
return CFBundleIsArchitectureLoadable(CPU_TYPE_X86_64);
} else {
// Arm Macs require at least 11.0 to run, so this branch will never be
// taken.
NOTREACHED();
return false;
}
}
void RequestRosettaInstallation(
const string16& title_text,
const string16& body_text,
OnceCallback<void(RosettaInstallationResult)> callback) {
DCHECK([NSThread isMainThread]);
if (IsRosettaInstalled()) {
std::move(callback).Run(RosettaInstallationResult::kAlreadyInstalled);
return;
}
static NSObject* current_rosetta_installation = nil;
if (current_rosetta_installation) {
if ([current_rosetta_installation
respondsToSelector:@selector(windowController)]) {
NSWindowController* windowController = [current_rosetta_installation
performSelector:@selector(windowController)];
if (windowController &&
[windowController isKindOfClass:[NSWindowController class]]) {
[windowController showWindow:nil];
}
}
return;
}
@autoreleasepool {
static const NoDestructor<scoped_nsobject<NSBundle>> bundle([]() {
scoped_nsobject<NSBundle> bundle(
[[NSBundle alloc] initWithPath:@"/System/Library/PrivateFrameworks/"
@"OAHSoftwareUpdate.framework"]);
if (![bundle load])
return scoped_nsobject<NSBundle>();
return bundle;
}());
if (!bundle.get()) {
std::move(callback).Run(RosettaInstallationResult::kFailedToAccessSPI);
return;
}
// The method being called is:
//
// - (void)[OAHSoftwareUpdateController
// startUpdateWithOptions:(NSDictionary*)options
// withHostWindow:(NSWindow*)window
// completion:(void (^)(BOOL, NSError*))block]
SEL selector = @selector(startUpdateWithOptions:withHostWindow:completion:);
scoped_nsobject<NSObject> controller(
[[NSClassFromString(@"OAHSoftwareUpdateController") alloc] init]);
NSMethodSignature* signature =
[controller methodSignatureForSelector:selector];
if (!signature) {
std::move(callback).Run(RosettaInstallationResult::kFailedToAccessSPI);
return;
}
if (strcmp(signature.methodReturnType, "v") != 0 ||
signature.numberOfArguments != 5 ||
strcmp([signature getArgumentTypeAtIndex:0], "@") != 0 ||
strcmp([signature getArgumentTypeAtIndex:1], ":") != 0 ||
strcmp([signature getArgumentTypeAtIndex:2], "@") != 0 ||
strcmp([signature getArgumentTypeAtIndex:3], "@") != 0 ||
strcmp([signature getArgumentTypeAtIndex:4], "@?") != 0) {
std::move(callback).Run(RosettaInstallationResult::kFailedToAccessSPI);
return;
}
NSInvocation* invocation =
[NSInvocation invocationWithMethodSignature:signature];
current_rosetta_installation = [controller.get() retain];
invocation.target = current_rosetta_installation;
invocation.selector = selector;
NSDictionary* options = @{
@"TitleText" : SysUTF16ToNSString(title_text),
@"BodyText" : SysUTF16ToNSString(body_text)
};
[invocation setArgument:&options atIndex:2];
NSWindow* window = nil;
[invocation setArgument:&window atIndex:3];
__block OnceCallback<void(RosettaInstallationResult)> block_callback =
std::move(callback);
auto completion = ^(BOOL success, NSError* error) {
// There _should_ be a valid callback and current_rosetta_installation
// here. However, crashes indicate that sometimes
// OAHSoftwareUpdateController performs a double-callback of the block.
// Therefore, be paranoid.
if (current_rosetta_installation) {
[current_rosetta_installation release];
current_rosetta_installation = nil;
}
if (block_callback) {
std::move(block_callback)
.Run(success ? RosettaInstallationResult::kInstallationSuccess
: RosettaInstallationResult::kInstallationFailure);
}
};
[invocation setArgument:&completion atIndex:4];
[invocation invoke];
}
}
#endif // ARCH_CPU_ARM64
bool RequestRosettaAheadOfTimeTranslation(
const std::vector<FilePath>& binaries) {
#if defined(ARCH_CPU_X86_64)
if (!ProcessIsTranslated())
return false;
#endif // ARCH_CPU_X86_64
// It's unclear if this is a BOOL or Boolean or bool. It's returning 0 or 1
// in w0, so just grab the result as an int and convert here.
// int rosetta_translate_binaries(const char*[] paths, int npaths)
using rosetta_translate_binaries_t = int (*)(const char*[], int);
static auto rosetta_translate_binaries = []() {
void* librosetta =
dlopen("/usr/lib/libRosetta.dylib", RTLD_LAZY | RTLD_LOCAL);
if (!librosetta)
return static_cast<rosetta_translate_binaries_t>(nullptr);
return reinterpret_cast<rosetta_translate_binaries_t>(
dlsym(librosetta, "rosetta_translate_binaries"));
}();
if (!rosetta_translate_binaries)
return false;
std::vector<std::string> paths;
std::vector<const char*> path_strs;
paths.reserve(binaries.size());
path_strs.reserve(paths.size());
for (const auto& binary : binaries) {
paths.push_back(FilePath::GetHFSDecomposedForm(binary.value()));
path_strs.push_back(paths.back().c_str());
}
ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
return rosetta_translate_binaries(path_strs.data(), path_strs.size());
}
} // namespace mac
} // namespace base