Bug 1690340 - Part 4: Insert the "Page Source" before the "Extensions for Developers...
[gecko.git] / xpcom / base / nsMacUtilsImpl.cpp
blob2cb9d68568c2443b33f8bd58f3db9e6dab0b2dda
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/dom/ContentChild.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 NS_IMPL_ISUPPORTS(nsMacUtilsImpl, nsIMacUtils)
35 using mozilla::StaticMutexAutoLock;
36 using mozilla::Unused;
38 #if defined(MOZ_SANDBOX)
39 StaticAutoPtr<nsCString> nsMacUtilsImpl::sCachedAppPath;
40 StaticMutex nsMacUtilsImpl::sCachedAppPathMutex;
41 #endif
43 std::atomic<uint32_t> nsMacUtilsImpl::sBundleArchMaskAtomic = 0;
45 #if defined(__aarch64__)
46 std::atomic<bool> nsMacUtilsImpl::sIsXULTranslated = false;
47 #endif
49 // Info.plist key associated with the developer repo path
50 #define MAC_DEV_REPO_KEY "MozillaDeveloperRepoPath"
51 // Info.plist key associated with the developer repo object directory
52 #define MAC_DEV_OBJ_KEY "MozillaDeveloperObjPath"
54 // Workaround this constant not being available in the macOS SDK
55 #define kCFBundleExecutableArchitectureARM64 0x0100000c
57 // Initialize with Unknown until we've checked if TCSM is available to set
58 Atomic<nsMacUtilsImpl::TCSMStatus> nsMacUtilsImpl::sTCSMStatus(TCSM_Unknown);
60 nsresult nsMacUtilsImpl::GetArchString(nsAString& aArchString) {
61 if (!mBinaryArchs.IsEmpty()) {
62 aArchString.Assign(mBinaryArchs);
63 return NS_OK;
66 uint32_t archMask = base::PROCESS_ARCH_INVALID;
67 nsresult rv = GetArchitecturesForBundle(&archMask);
68 NS_ENSURE_SUCCESS(rv, rv);
70 // The order in the string must always be the same so
71 // don't do this in the loop.
72 if (archMask & base::PROCESS_ARCH_PPC) {
73 mBinaryArchs.AppendLiteral("ppc");
76 if (archMask & base::PROCESS_ARCH_I386) {
77 if (!mBinaryArchs.IsEmpty()) {
78 mBinaryArchs.Append('-');
80 mBinaryArchs.AppendLiteral("i386");
83 if (archMask & base::PROCESS_ARCH_PPC_64) {
84 if (!mBinaryArchs.IsEmpty()) {
85 mBinaryArchs.Append('-');
87 mBinaryArchs.AppendLiteral("ppc64");
90 if (archMask & base::PROCESS_ARCH_X86_64) {
91 if (!mBinaryArchs.IsEmpty()) {
92 mBinaryArchs.Append('-');
94 mBinaryArchs.AppendLiteral("x86_64");
97 if (archMask & base::PROCESS_ARCH_ARM_64) {
98 if (!mBinaryArchs.IsEmpty()) {
99 mBinaryArchs.Append('-');
101 mBinaryArchs.AppendLiteral("arm64");
104 aArchString.Truncate();
105 aArchString.Assign(mBinaryArchs);
107 return (aArchString.IsEmpty() ? NS_ERROR_FAILURE : NS_OK);
110 NS_IMETHODIMP
111 nsMacUtilsImpl::GetArchitecturesInBinary(nsAString& aArchString) {
112 return GetArchString(aArchString);
115 // True when running under binary translation (Rosetta).
116 NS_IMETHODIMP
117 nsMacUtilsImpl::GetIsTranslated(bool* aIsTranslated) {
118 #ifdef __ppc__
119 static bool sInitialized = false;
121 // Initialize sIsNative to 1. If the sysctl fails because it doesn't
122 // exist, then translation is not possible, so the process must not be
123 // running translated.
124 static int32_t sIsNative = 1;
126 if (!sInitialized) {
127 size_t sz = sizeof(sIsNative);
128 sysctlbyname("sysctl.proc_native", &sIsNative, &sz, nullptr, 0);
129 sInitialized = true;
132 *aIsTranslated = !sIsNative;
133 #else
134 // Translation only exists for ppc code. Other architectures aren't
135 // translated.
136 *aIsTranslated = false;
137 #endif
139 return NS_OK;
142 #if defined(MOZ_SANDBOX)
143 // Get the path to the .app directory (aka bundle) for the parent process.
144 // When executing in the child process, this is the outer .app (such as
145 // Firefox.app) and not the inner .app containing the child process
146 // executable. We don't rely on the actual .app extension to allow for the
147 // bundle being renamed.
148 bool nsMacUtilsImpl::GetAppPath(nsCString& aAppPath) {
149 StaticMutexAutoLock lock(sCachedAppPathMutex);
150 if (sCachedAppPath) {
151 aAppPath.Assign(*sCachedAppPath);
152 return true;
155 nsAutoCString appPath;
156 nsAutoCString appBinaryPath(
157 (CommandLine::ForCurrentProcess()->argv()[0]).c_str());
159 // The binary path resides within the .app dir in Contents/MacOS,
160 // e.g., Firefox.app/Contents/MacOS/firefox. Search backwards in
161 // the binary path for the end of .app path.
162 auto pattern = "/Contents/MacOS/"_ns;
163 nsAutoCString::const_iterator start, end;
164 appBinaryPath.BeginReading(start);
165 appBinaryPath.EndReading(end);
166 if (RFindInReadable(pattern, start, end)) {
167 end = start;
168 appBinaryPath.BeginReading(start);
170 // If we're executing in a child process, get the parent .app path
171 // by searching backwards once more. The child executable resides
172 // in Firefox.app/Contents/MacOS/plugin-container/Contents/MacOS.
173 if (!XRE_IsParentProcess()) {
174 if (RFindInReadable(pattern, start, end)) {
175 end = start;
176 appBinaryPath.BeginReading(start);
177 } else {
178 return false;
182 appPath.Assign(Substring(start, end));
183 } else {
184 return false;
187 nsCOMPtr<nsIFile> app;
188 nsresult rv = NS_NewLocalFile(NS_ConvertUTF8toUTF16(appPath), true,
189 getter_AddRefs(app));
190 if (NS_FAILED(rv)) {
191 return false;
194 rv = app->Normalize();
195 if (NS_FAILED(rv)) {
196 return false;
198 app->GetNativePath(aAppPath);
200 if (!sCachedAppPath) {
201 sCachedAppPath = new nsCString(aAppPath);
203 if (NS_IsMainThread()) {
204 nsMacUtilsImpl::ClearCachedAppPathOnShutdown();
205 } else {
206 NS_DispatchToMainThread(NS_NewRunnableFunction(
207 "nsMacUtilsImpl::ClearCachedAppPathOnShutdown",
208 [] { nsMacUtilsImpl::ClearCachedAppPathOnShutdown(); }));
212 return true;
215 nsresult nsMacUtilsImpl::ClearCachedAppPathOnShutdown() {
216 MOZ_ASSERT(NS_IsMainThread());
217 ClearOnShutdown(&sCachedAppPath);
218 return NS_OK;
221 # if defined(DEBUG)
222 // If XPCOM_MEM_BLOAT_LOG or XPCOM_MEM_LEAK_LOG is set to a log file
223 // path, return the path to the parent directory (where sibling log
224 // files will be saved.)
225 nsresult nsMacUtilsImpl::GetBloatLogDir(nsCString& aDirectoryPath) {
226 nsAutoCString bloatLog(PR_GetEnv("XPCOM_MEM_BLOAT_LOG"));
227 if (bloatLog.IsEmpty()) {
228 bloatLog = PR_GetEnv("XPCOM_MEM_LEAK_LOG");
230 if (!bloatLog.IsEmpty() && bloatLog != "1" && bloatLog != "2") {
231 return GetDirectoryPath(bloatLog.get(), aDirectoryPath);
233 return NS_OK;
236 // Given a path to a file, return the directory which contains it.
237 nsresult nsMacUtilsImpl::GetDirectoryPath(const char* aPath,
238 nsCString& aDirectoryPath) {
239 nsresult rv = NS_OK;
240 nsCOMPtr<nsIFile> file = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
241 NS_ENSURE_SUCCESS(rv, rv);
243 rv = file->InitWithNativePath(nsDependentCString(aPath));
244 NS_ENSURE_SUCCESS(rv, rv);
246 nsCOMPtr<nsIFile> directoryFile;
247 rv = file->GetParent(getter_AddRefs(directoryFile));
248 NS_ENSURE_SUCCESS(rv, rv);
250 rv = directoryFile->Normalize();
251 NS_ENSURE_SUCCESS(rv, rv);
253 if (NS_FAILED(directoryFile->GetNativePath(aDirectoryPath))) {
254 MOZ_CRASH("Failed to get path for an nsIFile");
256 return NS_OK;
258 # endif /* DEBUG */
259 #endif /* MOZ_SANDBOX */
261 /* static */
262 bool nsMacUtilsImpl::IsTCSMAvailable() {
263 if (sTCSMStatus == TCSM_Unknown) {
264 uint32_t oldVal = 0;
265 size_t oldValSize = sizeof(oldVal);
266 int rv = sysctlbyname("kern.tcsm_available", &oldVal, &oldValSize, NULL, 0);
267 TCSMStatus newStatus;
268 if (rv < 0 || oldVal == 0) {
269 newStatus = TCSM_Unavailable;
270 } else {
271 newStatus = TCSM_Available;
273 // The value of sysctl kern.tcsm_available is the same for all
274 // threads within the same process. If another thread raced with us
275 // and initialized sTCSMStatus first (changing it from
276 // TCSM_Unknown), we can continue without needing to update it
277 // again. Hence, we ignore compareExchange's return value.
278 Unused << sTCSMStatus.compareExchange(TCSM_Unknown, newStatus);
280 return (sTCSMStatus == TCSM_Available);
283 /* static */
284 nsresult nsMacUtilsImpl::EnableTCSM() {
285 uint32_t newVal = 1;
286 int rv = sysctlbyname("kern.tcsm_enable", NULL, 0, &newVal, sizeof(newVal));
287 if (rv < 0) {
288 return NS_ERROR_UNEXPECTED;
290 return NS_OK;
294 * Intentionally return void so that failures will be ignored in non-debug
295 * builds. This method uses new sysctls which may not be as thoroughly tested
296 * and we don't want to cause crashes handling the failure due to an OS bug.
298 /* static */
299 void nsMacUtilsImpl::EnableTCSMIfAvailable() {
300 if (IsTCSMAvailable()) {
301 if (NS_FAILED(EnableTCSM())) {
302 NS_WARNING("Failed to enable TCSM");
304 MOZ_ASSERT(IsTCSMEnabled());
308 #if defined(DEBUG)
309 /* static */
310 bool nsMacUtilsImpl::IsTCSMEnabled() {
311 uint32_t oldVal = 0;
312 size_t oldValSize = sizeof(oldVal);
313 int rv = sysctlbyname("kern.tcsm_enable", &oldVal, &oldValSize, NULL, 0);
314 return (rv == 0) && (oldVal != 0);
316 #endif
318 // Returns 0 on error.
319 /* static */
320 uint32_t nsMacUtilsImpl::GetPhysicalCPUCount() {
321 uint32_t oldVal = 0;
322 size_t oldValSize = sizeof(oldVal);
323 int rv = sysctlbyname("hw.physicalcpu_max", &oldVal, &oldValSize, NULL, 0);
324 if (rv == -1) {
325 return 0;
327 return oldVal;
331 * Helper function to read a string value for a given key from the .app's
332 * Info.plist.
334 static nsresult GetStringValueFromBundlePlist(const nsAString& aKey,
335 nsAutoCString& aValue) {
336 CFBundleRef mainBundle = CFBundleGetMainBundle();
337 if (mainBundle == nullptr) {
338 return NS_ERROR_FAILURE;
341 // Read this app's bundle Info.plist as a dictionary
342 CFDictionaryRef bundleInfoDict = CFBundleGetInfoDictionary(mainBundle);
343 if (bundleInfoDict == nullptr) {
344 return NS_ERROR_FAILURE;
347 nsAutoCString keyAutoCString = NS_ConvertUTF16toUTF8(aKey);
348 CFStringRef key = CFStringCreateWithCString(
349 kCFAllocatorDefault, keyAutoCString.get(), kCFStringEncodingUTF8);
350 if (key == nullptr) {
351 return NS_ERROR_FAILURE;
354 CFStringRef value = (CFStringRef)CFDictionaryGetValue(bundleInfoDict, key);
355 CFRelease(key);
356 if (value == nullptr) {
357 return NS_ERROR_FAILURE;
360 CFIndex valueLength = CFStringGetLength(value);
361 if (valueLength == 0) {
362 return NS_ERROR_FAILURE;
365 const char* valueCString =
366 CFStringGetCStringPtr(value, kCFStringEncodingUTF8);
367 if (valueCString) {
368 aValue.Assign(valueCString);
369 return NS_OK;
372 CFIndex maxLength =
373 CFStringGetMaximumSizeForEncoding(valueLength, kCFStringEncodingUTF8) + 1;
374 char* valueBuffer = static_cast<char*>(moz_xmalloc(maxLength));
376 if (!CFStringGetCString(value, valueBuffer, maxLength,
377 kCFStringEncodingUTF8)) {
378 free(valueBuffer);
379 return NS_ERROR_FAILURE;
382 aValue.Assign(valueBuffer);
383 free(valueBuffer);
384 return NS_OK;
388 * Helper function for reading a path string from the .app's Info.plist
389 * and returning a directory object for that path with symlinks resolved.
391 static nsresult GetDirFromBundlePlist(const nsAString& aKey, nsIFile** aDir) {
392 nsresult rv;
394 nsAutoCString dirPath;
395 rv = GetStringValueFromBundlePlist(aKey, dirPath);
396 NS_ENSURE_SUCCESS(rv, rv);
398 nsCOMPtr<nsIFile> dir;
399 rv = NS_NewLocalFile(NS_ConvertUTF8toUTF16(dirPath), false,
400 getter_AddRefs(dir));
401 NS_ENSURE_SUCCESS(rv, rv);
403 rv = dir->Normalize();
404 NS_ENSURE_SUCCESS(rv, rv);
406 bool isDirectory = false;
407 rv = dir->IsDirectory(&isDirectory);
408 NS_ENSURE_SUCCESS(rv, rv);
409 if (!isDirectory) {
410 return NS_ERROR_FILE_NOT_DIRECTORY;
413 dir.swap(*aDir);
414 return NS_OK;
417 nsresult nsMacUtilsImpl::GetRepoDir(nsIFile** aRepoDir) {
418 #if defined(MOZ_SANDBOX)
419 MOZ_ASSERT(mozilla::IsDevelopmentBuild());
420 #endif
421 return GetDirFromBundlePlist(NS_LITERAL_STRING_FROM_CSTRING(MAC_DEV_REPO_KEY),
422 aRepoDir);
425 nsresult nsMacUtilsImpl::GetObjDir(nsIFile** aObjDir) {
426 #if defined(MOZ_SANDBOX)
427 MOZ_ASSERT(mozilla::IsDevelopmentBuild());
428 #endif
429 return GetDirFromBundlePlist(NS_LITERAL_STRING_FROM_CSTRING(MAC_DEV_OBJ_KEY),
430 aObjDir);
433 /* static */
434 nsresult nsMacUtilsImpl::GetArchitecturesForBundle(uint32_t* aArchMask) {
435 MOZ_ASSERT(aArchMask);
437 *aArchMask = sBundleArchMaskAtomic;
438 if (*aArchMask != 0) {
439 return NS_OK;
442 CFBundleRef mainBundle = ::CFBundleGetMainBundle();
443 if (!mainBundle) {
444 return NS_ERROR_FAILURE;
447 CFArrayRef archList = ::CFBundleCopyExecutableArchitectures(mainBundle);
448 if (!archList) {
449 return NS_ERROR_FAILURE;
452 CFIndex archCount = ::CFArrayGetCount(archList);
453 for (CFIndex i = 0; i < archCount; i++) {
454 CFNumberRef arch =
455 static_cast<CFNumberRef>(::CFArrayGetValueAtIndex(archList, i));
457 int archInt = 0;
458 if (!::CFNumberGetValue(arch, kCFNumberIntType, &archInt)) {
459 ::CFRelease(archList);
460 return NS_ERROR_FAILURE;
463 if (archInt == kCFBundleExecutableArchitecturePPC) {
464 *aArchMask |= base::PROCESS_ARCH_PPC;
465 } else if (archInt == kCFBundleExecutableArchitectureI386) {
466 *aArchMask |= base::PROCESS_ARCH_I386;
467 } else if (archInt == kCFBundleExecutableArchitecturePPC64) {
468 *aArchMask |= base::PROCESS_ARCH_PPC_64;
469 } else if (archInt == kCFBundleExecutableArchitectureX86_64) {
470 *aArchMask |= base::PROCESS_ARCH_X86_64;
471 } else if (archInt == kCFBundleExecutableArchitectureARM64) {
472 *aArchMask |= base::PROCESS_ARCH_ARM_64;
476 ::CFRelease(archList);
478 sBundleArchMaskAtomic = *aArchMask;
480 return NS_OK;
483 /* static */
484 nsresult nsMacUtilsImpl::GetArchitecturesForBinary(const char* aPath,
485 uint32_t* aArchMask) {
486 MOZ_ASSERT(aArchMask);
488 *aArchMask = 0;
490 CFURLRef url = ::CFURLCreateFromFileSystemRepresentation(
491 kCFAllocatorDefault, (const UInt8*)aPath, strlen(aPath), false);
492 if (!url) {
493 return NS_ERROR_FAILURE;
496 CFArrayRef archs = ::CFBundleCopyExecutableArchitecturesForURL(url);
497 if (!archs) {
498 CFRelease(url);
499 return NS_ERROR_FAILURE;
502 CFIndex archCount = ::CFArrayGetCount(archs);
503 for (CFIndex i = 0; i < archCount; i++) {
504 CFNumberRef currentArch =
505 static_cast<CFNumberRef>(::CFArrayGetValueAtIndex(archs, i));
506 int currentArchInt = 0;
507 if (!::CFNumberGetValue(currentArch, kCFNumberIntType, &currentArchInt)) {
508 continue;
510 switch (currentArchInt) {
511 case kCFBundleExecutableArchitectureX86_64:
512 *aArchMask |= base::PROCESS_ARCH_X86_64;
513 break;
514 case kCFBundleExecutableArchitectureARM64:
515 *aArchMask |= base::PROCESS_ARCH_ARM_64;
516 break;
517 default:
518 break;
522 CFRelease(url);
523 CFRelease(archs);
525 // We expect x86 or ARM64 or both.
526 if (*aArchMask == 0) {
527 return NS_ERROR_UNEXPECTED;
530 return NS_OK;
533 #if defined(__aarch64__)
534 // Pre-translate XUL so that x64 child processes launched after this
535 // translation will not incur the translation overhead delaying startup.
536 // Returns 1 if translation is in progress, -1 on an error encountered before
537 // translation, and otherwise returns the result of rosetta_translate_binaries.
538 /* static */
539 int nsMacUtilsImpl::PreTranslateXUL() {
540 bool expected = false;
541 if (!sIsXULTranslated.compare_exchange_strong(expected, true)) {
542 // Translation is already done or in progress.
543 return 1;
546 // Get the path to XUL by first getting the
547 // outer .app path and appending the path to XUL.
548 nsCString xulPath;
549 if (!GetAppPath(xulPath)) {
550 return -1;
552 xulPath.Append("/Contents/MacOS/XUL");
554 return PreTranslateBinary(xulPath);
557 // Use Chromium's method to pre-translate the provided binary using the
558 // undocumented function "rosetta_translate_binaries" from libRosetta.dylib.
559 // Re-translating the same binary does not cause translation to occur again.
560 // Returns -1 on an error encountered before translation, otherwise returns
561 // the rosetta_translate_binaries result. This method is partly copied from
562 // Chromium code.
563 /* static */
564 int nsMacUtilsImpl::PreTranslateBinary(nsCString aBinaryPath) {
565 // Do not attempt to use this in child processes. Child
566 // processes executing should already be translated and
567 // sandboxing may interfere with translation.
568 MOZ_ASSERT(XRE_IsParentProcess());
569 if (!XRE_IsParentProcess()) {
570 return -1;
573 // Translation can take several seconds and therefore
574 // should not be done on the main thread.
575 MOZ_ASSERT(!NS_IsMainThread());
576 if (NS_IsMainThread()) {
577 return -1;
580 // @available() is not available for macOS 11 at this time so use
581 // -Wunguarded-availability-new to avoid compiler warnings caused
582 // by an earlier minimum SDK. ARM64 builds require the 11.0 SDK and
583 // can not be run on earlier OS versions so this is not a concern.
584 # pragma clang diagnostic push
585 # pragma clang diagnostic ignored "-Wunguarded-availability-new"
586 // If Rosetta is not installed, do not proceed.
587 if (!CFBundleIsArchitectureLoadable(CPU_TYPE_X86_64)) {
588 return -1;
590 # pragma clang diagnostic pop
592 if (aBinaryPath.IsEmpty()) {
593 return -1;
596 // int rosetta_translate_binaries(const char*[] paths, int npaths)
597 using rosetta_translate_binaries_t = int (*)(const char*[], int);
599 static auto rosetta_translate_binaries = []() {
600 void* libRosetta =
601 dlopen("/usr/lib/libRosetta.dylib", RTLD_LAZY | RTLD_LOCAL);
602 if (!libRosetta) {
603 return static_cast<rosetta_translate_binaries_t>(nullptr);
606 return reinterpret_cast<rosetta_translate_binaries_t>(
607 dlsym(libRosetta, "rosetta_translate_binaries"));
608 }();
610 if (!rosetta_translate_binaries) {
611 return -1;
614 const char* pathPtr = aBinaryPath.get();
615 return rosetta_translate_binaries(&pathPtr, 1);
618 #endif