Bug 1867190 - Add prefs for PHC probablities r=glandium
[gecko.git] / xpcom / base / nsMacUtilsImpl.cpp
blob86227b7acae6316586b09210e2674300f2b07a42
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "nsMacUtilsImpl.h"
9 #include "base/command_line.h"
10 #include "base/process_util.h"
11 #include "mozilla/ClearOnShutdown.h"
12 #include "mozilla/Omnijar.h"
13 #include "nsDirectoryServiceDefs.h"
14 #include "nsCOMPtr.h"
15 #include "nsComponentManagerUtils.h"
16 #include "nsIFile.h"
17 #include "nsServiceManagerUtils.h"
18 #include "nsThreadUtils.h"
19 #include "nsXULAppAPI.h"
20 #include "prenv.h"
22 #if defined(MOZ_SANDBOX)
23 # include "mozilla/SandboxSettings.h"
24 #endif
26 #include <CoreFoundation/CoreFoundation.h>
27 #include <CoreServices/CoreServices.h>
28 #if defined(__aarch64__)
29 # include <dlfcn.h>
30 #endif
31 #include <sys/sysctl.h>
33 using mozilla::StaticMutexAutoLock;
34 using mozilla::Unused;
36 #if defined(MOZ_SANDBOX) || defined(__aarch64__)
37 // For thread safe setting/checking of sCachedAppPath
38 static StaticMutex sCachedAppPathMutex;
40 // Cache the appDir returned from GetAppPath to avoid doing I/O
41 static StaticAutoPtr<nsCString> sCachedAppPath
42 MOZ_GUARDED_BY(sCachedAppPathMutex);
43 #endif
45 // The cached machine architectures of the .app bundle which can
46 // be multiple architectures for universal binaries.
47 static std::atomic<uint32_t> sBundleArchMaskAtomic = 0;
49 #if defined(__aarch64__)
50 // Limit XUL translation to one attempt
51 static std::atomic<bool> sIsXULTranslated = false;
52 #endif
54 // Info.plist key associated with the developer repo path
55 #define MAC_DEV_REPO_KEY "MozillaDeveloperRepoPath"
56 // Info.plist key associated with the developer repo object directory
57 #define MAC_DEV_OBJ_KEY "MozillaDeveloperObjPath"
59 // Workaround this constant not being available in the macOS SDK
60 #define kCFBundleExecutableArchitectureARM64 0x0100000c
62 enum TCSMStatus { TCSM_Unknown = 0, TCSM_Available, TCSM_Unavailable };
64 // Initialize with Unknown until we've checked if TCSM is available to set
65 static Atomic<TCSMStatus> sTCSMStatus(TCSM_Unknown);
67 #if defined(MOZ_SANDBOX) || defined(__aarch64__)
69 // Utility method to call ClearOnShutdown() on the main thread
70 static nsresult ClearCachedAppPathOnShutdown() {
71 MOZ_ASSERT(NS_IsMainThread());
72 ClearOnShutdown(&sCachedAppPath);
73 return NS_OK;
76 // Get the path to the .app directory (aka bundle) for the parent process.
77 // When executing in the child process, this is the outer .app (such as
78 // Firefox.app) and not the inner .app containing the child process
79 // executable. We don't rely on the actual .app extension to allow for the
80 // bundle being renamed.
81 bool nsMacUtilsImpl::GetAppPath(nsCString& aAppPath) {
82 StaticMutexAutoLock lock(sCachedAppPathMutex);
83 if (sCachedAppPath) {
84 aAppPath.Assign(*sCachedAppPath);
85 return true;
88 nsAutoCString appPath;
89 nsAutoCString appBinaryPath(
90 (CommandLine::ForCurrentProcess()->argv()[0]).c_str());
92 // The binary path resides within the .app dir in Contents/MacOS,
93 // e.g., Firefox.app/Contents/MacOS/firefox. Search backwards in
94 // the binary path for the end of .app path.
95 auto pattern = "/Contents/MacOS/"_ns;
96 nsAutoCString::const_iterator start, end;
97 appBinaryPath.BeginReading(start);
98 appBinaryPath.EndReading(end);
99 if (RFindInReadable(pattern, start, end)) {
100 end = start;
101 appBinaryPath.BeginReading(start);
103 // If we're executing in a child process, get the parent .app path
104 // by searching backwards once more. The child executable resides
105 // in Firefox.app/Contents/MacOS/plugin-container/Contents/MacOS.
106 if (!XRE_IsParentProcess()) {
107 if (RFindInReadable(pattern, start, end)) {
108 end = start;
109 appBinaryPath.BeginReading(start);
110 } else {
111 return false;
115 appPath.Assign(Substring(start, end));
116 } else {
117 return false;
120 nsCOMPtr<nsIFile> app;
121 nsresult rv = NS_NewLocalFile(NS_ConvertUTF8toUTF16(appPath), true,
122 getter_AddRefs(app));
123 if (NS_FAILED(rv)) {
124 return false;
127 rv = app->Normalize();
128 if (NS_FAILED(rv)) {
129 return false;
131 app->GetNativePath(aAppPath);
133 if (!sCachedAppPath) {
134 sCachedAppPath = new nsCString(aAppPath);
136 if (NS_IsMainThread()) {
137 ClearCachedAppPathOnShutdown();
138 } else {
139 NS_DispatchToMainThread(
140 NS_NewRunnableFunction("ClearCachedAppPathOnShutdown",
141 [] { ClearCachedAppPathOnShutdown(); }));
145 return true;
148 #endif /* MOZ_SANDBOX || __aarch64__ */
150 #if defined(MOZ_SANDBOX) && defined(DEBUG)
151 // If XPCOM_MEM_BLOAT_LOG or XPCOM_MEM_LEAK_LOG is set to a log file
152 // path, return the path to the parent directory (where sibling log
153 // files will be saved.)
154 nsresult nsMacUtilsImpl::GetBloatLogDir(nsCString& aDirectoryPath) {
155 nsAutoCString bloatLog(PR_GetEnv("XPCOM_MEM_BLOAT_LOG"));
156 if (bloatLog.IsEmpty()) {
157 bloatLog = PR_GetEnv("XPCOM_MEM_LEAK_LOG");
159 if (!bloatLog.IsEmpty() && bloatLog != "1" && bloatLog != "2") {
160 return GetDirectoryPath(bloatLog.get(), aDirectoryPath);
162 return NS_OK;
165 // Given a path to a file, return the directory which contains it.
166 nsresult nsMacUtilsImpl::GetDirectoryPath(const char* aPath,
167 nsCString& aDirectoryPath) {
168 nsresult rv = NS_OK;
169 nsCOMPtr<nsIFile> file = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
170 NS_ENSURE_SUCCESS(rv, rv);
172 rv = file->InitWithNativePath(nsDependentCString(aPath));
173 NS_ENSURE_SUCCESS(rv, rv);
175 nsCOMPtr<nsIFile> directoryFile;
176 rv = file->GetParent(getter_AddRefs(directoryFile));
177 NS_ENSURE_SUCCESS(rv, rv);
179 rv = directoryFile->Normalize();
180 NS_ENSURE_SUCCESS(rv, rv);
182 if (NS_FAILED(directoryFile->GetNativePath(aDirectoryPath))) {
183 MOZ_CRASH("Failed to get path for an nsIFile");
185 return NS_OK;
187 #endif /* MOZ_SANDBOX && DEBUG */
189 /* static */
190 bool nsMacUtilsImpl::IsTCSMAvailable() {
191 if (sTCSMStatus == TCSM_Unknown) {
192 uint32_t oldVal = 0;
193 size_t oldValSize = sizeof(oldVal);
194 int rv = sysctlbyname("kern.tcsm_available", &oldVal, &oldValSize, NULL, 0);
195 TCSMStatus newStatus;
196 if (rv < 0 || oldVal == 0) {
197 newStatus = TCSM_Unavailable;
198 } else {
199 newStatus = TCSM_Available;
201 // The value of sysctl kern.tcsm_available is the same for all
202 // threads within the same process. If another thread raced with us
203 // and initialized sTCSMStatus first (changing it from
204 // TCSM_Unknown), we can continue without needing to update it
205 // again. Hence, we ignore compareExchange's return value.
206 Unused << sTCSMStatus.compareExchange(TCSM_Unknown, newStatus);
208 return (sTCSMStatus == TCSM_Available);
211 static nsresult EnableTCSM() {
212 uint32_t newVal = 1;
213 int rv = sysctlbyname("kern.tcsm_enable", NULL, 0, &newVal, sizeof(newVal));
214 if (rv < 0) {
215 return NS_ERROR_UNEXPECTED;
217 return NS_OK;
220 #if defined(DEBUG)
221 static bool IsTCSMEnabled() {
222 uint32_t oldVal = 0;
223 size_t oldValSize = sizeof(oldVal);
224 int rv = sysctlbyname("kern.tcsm_enable", &oldVal, &oldValSize, NULL, 0);
225 return (rv == 0) && (oldVal != 0);
227 #endif
230 * Intentionally return void so that failures will be ignored in non-debug
231 * builds. This method uses new sysctls which may not be as thoroughly tested
232 * and we don't want to cause crashes handling the failure due to an OS bug.
234 /* static */
235 void nsMacUtilsImpl::EnableTCSMIfAvailable() {
236 if (IsTCSMAvailable()) {
237 if (NS_FAILED(EnableTCSM())) {
238 NS_WARNING("Failed to enable TCSM");
240 MOZ_ASSERT(IsTCSMEnabled());
244 // Returns 0 on error.
245 /* static */
246 uint32_t nsMacUtilsImpl::GetPhysicalCPUCount() {
247 uint32_t oldVal = 0;
248 size_t oldValSize = sizeof(oldVal);
249 int rv = sysctlbyname("hw.physicalcpu_max", &oldVal, &oldValSize, NULL, 0);
250 if (rv == -1) {
251 return 0;
253 return oldVal;
257 * Helper function to read a string value for a given key from the .app's
258 * Info.plist.
260 static nsresult GetStringValueFromBundlePlist(const nsAString& aKey,
261 nsAutoCString& aValue) {
262 CFBundleRef mainBundle = CFBundleGetMainBundle();
263 if (mainBundle == nullptr) {
264 return NS_ERROR_FAILURE;
267 // Read this app's bundle Info.plist as a dictionary
268 CFDictionaryRef bundleInfoDict = CFBundleGetInfoDictionary(mainBundle);
269 if (bundleInfoDict == nullptr) {
270 return NS_ERROR_FAILURE;
273 nsAutoCString keyAutoCString = NS_ConvertUTF16toUTF8(aKey);
274 CFStringRef key = CFStringCreateWithCString(
275 kCFAllocatorDefault, keyAutoCString.get(), kCFStringEncodingUTF8);
276 if (key == nullptr) {
277 return NS_ERROR_FAILURE;
280 CFStringRef value = (CFStringRef)CFDictionaryGetValue(bundleInfoDict, key);
281 CFRelease(key);
282 if (value == nullptr) {
283 return NS_ERROR_FAILURE;
286 CFIndex valueLength = CFStringGetLength(value);
287 if (valueLength == 0) {
288 return NS_ERROR_FAILURE;
291 const char* valueCString =
292 CFStringGetCStringPtr(value, kCFStringEncodingUTF8);
293 if (valueCString) {
294 aValue.Assign(valueCString);
295 return NS_OK;
298 CFIndex maxLength =
299 CFStringGetMaximumSizeForEncoding(valueLength, kCFStringEncodingUTF8) + 1;
300 char* valueBuffer = static_cast<char*>(moz_xmalloc(maxLength));
302 if (!CFStringGetCString(value, valueBuffer, maxLength,
303 kCFStringEncodingUTF8)) {
304 free(valueBuffer);
305 return NS_ERROR_FAILURE;
308 aValue.Assign(valueBuffer);
309 free(valueBuffer);
310 return NS_OK;
314 * Helper function for reading a path string from the .app's Info.plist
315 * and returning a directory object for that path with symlinks resolved.
317 static nsresult GetDirFromBundlePlist(const nsAString& aKey, nsIFile** aDir) {
318 nsresult rv;
320 nsAutoCString dirPath;
321 rv = GetStringValueFromBundlePlist(aKey, dirPath);
322 NS_ENSURE_SUCCESS(rv, rv);
324 nsCOMPtr<nsIFile> dir;
325 rv = NS_NewLocalFile(NS_ConvertUTF8toUTF16(dirPath), false,
326 getter_AddRefs(dir));
327 NS_ENSURE_SUCCESS(rv, rv);
329 rv = dir->Normalize();
330 NS_ENSURE_SUCCESS(rv, rv);
332 bool isDirectory = false;
333 rv = dir->IsDirectory(&isDirectory);
334 NS_ENSURE_SUCCESS(rv, rv);
335 if (!isDirectory) {
336 return NS_ERROR_FILE_NOT_DIRECTORY;
339 dir.swap(*aDir);
340 return NS_OK;
343 nsresult nsMacUtilsImpl::GetRepoDir(nsIFile** aRepoDir) {
344 #if defined(MOZ_SANDBOX)
345 MOZ_ASSERT(!mozilla::IsPackagedBuild());
346 #endif
347 return GetDirFromBundlePlist(NS_LITERAL_STRING_FROM_CSTRING(MAC_DEV_REPO_KEY),
348 aRepoDir);
351 nsresult nsMacUtilsImpl::GetObjDir(nsIFile** aObjDir) {
352 #if defined(MOZ_SANDBOX)
353 MOZ_ASSERT(!mozilla::IsPackagedBuild());
354 #endif
355 return GetDirFromBundlePlist(NS_LITERAL_STRING_FROM_CSTRING(MAC_DEV_OBJ_KEY),
356 aObjDir);
359 /* static */
360 nsresult nsMacUtilsImpl::GetArchitecturesForBundle(uint32_t* aArchMask) {
361 MOZ_ASSERT(aArchMask);
363 *aArchMask = sBundleArchMaskAtomic;
364 if (*aArchMask != 0) {
365 return NS_OK;
368 CFBundleRef mainBundle = ::CFBundleGetMainBundle();
369 if (!mainBundle) {
370 return NS_ERROR_FAILURE;
373 CFArrayRef archList = ::CFBundleCopyExecutableArchitectures(mainBundle);
374 if (!archList) {
375 return NS_ERROR_FAILURE;
378 CFIndex archCount = ::CFArrayGetCount(archList);
379 for (CFIndex i = 0; i < archCount; i++) {
380 CFNumberRef arch =
381 static_cast<CFNumberRef>(::CFArrayGetValueAtIndex(archList, i));
383 int archInt = 0;
384 if (!::CFNumberGetValue(arch, kCFNumberIntType, &archInt)) {
385 ::CFRelease(archList);
386 return NS_ERROR_FAILURE;
389 if (archInt == kCFBundleExecutableArchitecturePPC) {
390 *aArchMask |= base::PROCESS_ARCH_PPC;
391 } else if (archInt == kCFBundleExecutableArchitectureI386) {
392 *aArchMask |= base::PROCESS_ARCH_I386;
393 } else if (archInt == kCFBundleExecutableArchitecturePPC64) {
394 *aArchMask |= base::PROCESS_ARCH_PPC_64;
395 } else if (archInt == kCFBundleExecutableArchitectureX86_64) {
396 *aArchMask |= base::PROCESS_ARCH_X86_64;
397 } else if (archInt == kCFBundleExecutableArchitectureARM64) {
398 *aArchMask |= base::PROCESS_ARCH_ARM_64;
402 ::CFRelease(archList);
404 sBundleArchMaskAtomic = *aArchMask;
406 return NS_OK;
409 /* static */
410 nsresult nsMacUtilsImpl::GetArchitecturesForBinary(const char* aPath,
411 uint32_t* aArchMask) {
412 MOZ_ASSERT(aArchMask);
414 *aArchMask = 0;
416 CFURLRef url = ::CFURLCreateFromFileSystemRepresentation(
417 kCFAllocatorDefault, (const UInt8*)aPath, strlen(aPath), false);
418 if (!url) {
419 return NS_ERROR_FAILURE;
422 CFArrayRef archs = ::CFBundleCopyExecutableArchitecturesForURL(url);
423 if (!archs) {
424 CFRelease(url);
425 return NS_ERROR_FAILURE;
428 CFIndex archCount = ::CFArrayGetCount(archs);
429 for (CFIndex i = 0; i < archCount; i++) {
430 CFNumberRef currentArch =
431 static_cast<CFNumberRef>(::CFArrayGetValueAtIndex(archs, i));
432 int currentArchInt = 0;
433 if (!::CFNumberGetValue(currentArch, kCFNumberIntType, &currentArchInt)) {
434 continue;
436 switch (currentArchInt) {
437 case kCFBundleExecutableArchitectureX86_64:
438 *aArchMask |= base::PROCESS_ARCH_X86_64;
439 break;
440 case kCFBundleExecutableArchitectureARM64:
441 *aArchMask |= base::PROCESS_ARCH_ARM_64;
442 break;
443 default:
444 break;
448 CFRelease(url);
449 CFRelease(archs);
451 // We expect x86 or ARM64 or both.
452 if (*aArchMask == 0) {
453 return NS_ERROR_UNEXPECTED;
456 return NS_OK;
459 #if defined(__aarch64__)
460 // Pre-translate XUL so that x64 child processes launched after this
461 // translation will not incur the translation overhead delaying startup.
462 // Returns 1 if translation is in progress, -1 on an error encountered before
463 // translation, and otherwise returns the result of rosetta_translate_binaries.
464 /* static */
465 int nsMacUtilsImpl::PreTranslateXUL() {
466 bool expected = false;
467 if (!sIsXULTranslated.compare_exchange_strong(expected, true)) {
468 // Translation is already done or in progress.
469 return 1;
472 // Get the path to XUL by first getting the
473 // outer .app path and appending the path to XUL.
474 nsCString xulPath;
475 if (!GetAppPath(xulPath)) {
476 return -1;
478 xulPath.Append("/Contents/MacOS/XUL");
480 return PreTranslateBinary(xulPath);
483 // Use Chromium's method to pre-translate the provided binary using the
484 // undocumented function "rosetta_translate_binaries" from libRosetta.dylib.
485 // Re-translating the same binary does not cause translation to occur again.
486 // Returns -1 on an error encountered before translation, otherwise returns
487 // the rosetta_translate_binaries result. This method is partly copied from
488 // Chromium code.
489 /* static */
490 int nsMacUtilsImpl::PreTranslateBinary(nsCString aBinaryPath) {
491 // Do not attempt to use this in child processes. Child
492 // processes executing should already be translated and
493 // sandboxing may interfere with translation.
494 MOZ_ASSERT(XRE_IsParentProcess());
495 if (!XRE_IsParentProcess()) {
496 return -1;
499 // Translation can take several seconds and therefore
500 // should not be done on the main thread.
501 MOZ_ASSERT(!NS_IsMainThread());
502 if (NS_IsMainThread()) {
503 return -1;
506 // @available() is not available for macOS 11 at this time so use
507 // -Wunguarded-availability-new to avoid compiler warnings caused
508 // by an earlier minimum SDK. ARM64 builds require the 11.0 SDK and
509 // can not be run on earlier OS versions so this is not a concern.
510 # pragma clang diagnostic push
511 # pragma clang diagnostic ignored "-Wunguarded-availability-new"
512 // If Rosetta is not installed, do not proceed.
513 if (!CFBundleIsArchitectureLoadable(CPU_TYPE_X86_64)) {
514 return -1;
516 # pragma clang diagnostic pop
518 if (aBinaryPath.IsEmpty()) {
519 return -1;
522 // int rosetta_translate_binaries(const char*[] paths, int npaths)
523 using rosetta_translate_binaries_t = int (*)(const char*[], int);
525 static auto rosetta_translate_binaries = []() {
526 void* libRosetta =
527 dlopen("/usr/lib/libRosetta.dylib", RTLD_LAZY | RTLD_LOCAL);
528 if (!libRosetta) {
529 return static_cast<rosetta_translate_binaries_t>(nullptr);
532 return reinterpret_cast<rosetta_translate_binaries_t>(
533 dlsym(libRosetta, "rosetta_translate_binaries"));
534 }();
536 if (!rosetta_translate_binaries) {
537 return -1;
540 const char* pathPtr = aBinaryPath.get();
541 return rosetta_translate_binaries(&pathPtr, 1);
544 #endif