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"
15 #include "nsComponentManagerUtils.h"
17 #include "nsServiceManagerUtils.h"
18 #include "nsThreadUtils.h"
19 #include "nsXULAppAPI.h"
22 #if defined(MOZ_SANDBOX)
23 # include "mozilla/SandboxSettings.h"
26 #include <CoreFoundation/CoreFoundation.h>
27 #include <CoreServices/CoreServices.h>
28 #if defined(__aarch64__)
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
;
43 std::atomic
<uint32_t> nsMacUtilsImpl::sBundleArchMaskAtomic
= 0;
45 #if defined(__aarch64__)
46 std::atomic
<bool> nsMacUtilsImpl::sIsXULTranslated
= false;
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
);
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
);
111 nsMacUtilsImpl::GetArchitecturesInBinary(nsAString
& aArchString
) {
112 return GetArchString(aArchString
);
115 // True when running under binary translation (Rosetta).
117 nsMacUtilsImpl::GetIsTranslated(bool* aIsTranslated
) {
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;
127 size_t sz
= sizeof(sIsNative
);
128 sysctlbyname("sysctl.proc_native", &sIsNative
, &sz
, nullptr, 0);
132 *aIsTranslated
= !sIsNative
;
134 // Translation only exists for ppc code. Other architectures aren't
136 *aIsTranslated
= false;
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
);
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
)) {
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
)) {
176 appBinaryPath
.BeginReading(start
);
182 appPath
.Assign(Substring(start
, end
));
187 nsCOMPtr
<nsIFile
> app
;
188 nsresult rv
= NS_NewLocalFile(NS_ConvertUTF8toUTF16(appPath
), true,
189 getter_AddRefs(app
));
194 rv
= app
->Normalize();
198 app
->GetNativePath(aAppPath
);
200 if (!sCachedAppPath
) {
201 sCachedAppPath
= new nsCString(aAppPath
);
203 if (NS_IsMainThread()) {
204 nsMacUtilsImpl::ClearCachedAppPathOnShutdown();
206 NS_DispatchToMainThread(NS_NewRunnableFunction(
207 "nsMacUtilsImpl::ClearCachedAppPathOnShutdown",
208 [] { nsMacUtilsImpl::ClearCachedAppPathOnShutdown(); }));
215 nsresult
nsMacUtilsImpl::ClearCachedAppPathOnShutdown() {
216 MOZ_ASSERT(NS_IsMainThread());
217 ClearOnShutdown(&sCachedAppPath
);
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
);
236 // Given a path to a file, return the directory which contains it.
237 nsresult
nsMacUtilsImpl::GetDirectoryPath(const char* aPath
,
238 nsCString
& aDirectoryPath
) {
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");
259 #endif /* MOZ_SANDBOX */
262 bool nsMacUtilsImpl::IsTCSMAvailable() {
263 if (sTCSMStatus
== TCSM_Unknown
) {
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
;
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
);
284 nsresult
nsMacUtilsImpl::EnableTCSM() {
286 int rv
= sysctlbyname("kern.tcsm_enable", NULL
, 0, &newVal
, sizeof(newVal
));
288 return NS_ERROR_UNEXPECTED
;
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.
299 void nsMacUtilsImpl::EnableTCSMIfAvailable() {
300 if (IsTCSMAvailable()) {
301 if (NS_FAILED(EnableTCSM())) {
302 NS_WARNING("Failed to enable TCSM");
304 MOZ_ASSERT(IsTCSMEnabled());
310 bool nsMacUtilsImpl::IsTCSMEnabled() {
312 size_t oldValSize
= sizeof(oldVal
);
313 int rv
= sysctlbyname("kern.tcsm_enable", &oldVal
, &oldValSize
, NULL
, 0);
314 return (rv
== 0) && (oldVal
!= 0);
318 // Returns 0 on error.
320 uint32_t nsMacUtilsImpl::GetPhysicalCPUCount() {
322 size_t oldValSize
= sizeof(oldVal
);
323 int rv
= sysctlbyname("hw.physicalcpu_max", &oldVal
, &oldValSize
, NULL
, 0);
331 * Helper function to read a string value for a given key from the .app's
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
);
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
);
368 aValue
.Assign(valueCString
);
373 CFStringGetMaximumSizeForEncoding(valueLength
, kCFStringEncodingUTF8
) + 1;
374 char* valueBuffer
= static_cast<char*>(moz_xmalloc(maxLength
));
376 if (!CFStringGetCString(value
, valueBuffer
, maxLength
,
377 kCFStringEncodingUTF8
)) {
379 return NS_ERROR_FAILURE
;
382 aValue
.Assign(valueBuffer
);
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
) {
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
);
410 return NS_ERROR_FILE_NOT_DIRECTORY
;
417 nsresult
nsMacUtilsImpl::GetRepoDir(nsIFile
** aRepoDir
) {
418 #if defined(MOZ_SANDBOX)
419 MOZ_ASSERT(mozilla::IsDevelopmentBuild());
421 return GetDirFromBundlePlist(NS_LITERAL_STRING_FROM_CSTRING(MAC_DEV_REPO_KEY
),
425 nsresult
nsMacUtilsImpl::GetObjDir(nsIFile
** aObjDir
) {
426 #if defined(MOZ_SANDBOX)
427 MOZ_ASSERT(mozilla::IsDevelopmentBuild());
429 return GetDirFromBundlePlist(NS_LITERAL_STRING_FROM_CSTRING(MAC_DEV_OBJ_KEY
),
434 nsresult
nsMacUtilsImpl::GetArchitecturesForBundle(uint32_t* aArchMask
) {
435 MOZ_ASSERT(aArchMask
);
437 *aArchMask
= sBundleArchMaskAtomic
;
438 if (*aArchMask
!= 0) {
442 CFBundleRef mainBundle
= ::CFBundleGetMainBundle();
444 return NS_ERROR_FAILURE
;
447 CFArrayRef archList
= ::CFBundleCopyExecutableArchitectures(mainBundle
);
449 return NS_ERROR_FAILURE
;
452 CFIndex archCount
= ::CFArrayGetCount(archList
);
453 for (CFIndex i
= 0; i
< archCount
; i
++) {
455 static_cast<CFNumberRef
>(::CFArrayGetValueAtIndex(archList
, i
));
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
;
484 nsresult
nsMacUtilsImpl::GetArchitecturesForBinary(const char* aPath
,
485 uint32_t* aArchMask
) {
486 MOZ_ASSERT(aArchMask
);
490 CFURLRef url
= ::CFURLCreateFromFileSystemRepresentation(
491 kCFAllocatorDefault
, (const UInt8
*)aPath
, strlen(aPath
), false);
493 return NS_ERROR_FAILURE
;
496 CFArrayRef archs
= ::CFBundleCopyExecutableArchitecturesForURL(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
, ¤tArchInt
)) {
510 switch (currentArchInt
) {
511 case kCFBundleExecutableArchitectureX86_64
:
512 *aArchMask
|= base::PROCESS_ARCH_X86_64
;
514 case kCFBundleExecutableArchitectureARM64
:
515 *aArchMask
|= base::PROCESS_ARCH_ARM_64
;
525 // We expect x86 or ARM64 or both.
526 if (*aArchMask
== 0) {
527 return NS_ERROR_UNEXPECTED
;
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.
539 int nsMacUtilsImpl::PreTranslateXUL() {
540 bool expected
= false;
541 if (!sIsXULTranslated
.compare_exchange_strong(expected
, true)) {
542 // Translation is already done or in progress.
546 // Get the path to XUL by first getting the
547 // outer .app path and appending the path to XUL.
549 if (!GetAppPath(xulPath
)) {
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
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()) {
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()) {
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
)) {
590 # pragma clang diagnostic pop
592 if (aBinaryPath
.IsEmpty()) {
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
= []() {
601 dlopen("/usr/lib/libRosetta.dylib", RTLD_LAZY
| RTLD_LOCAL
);
603 return static_cast<rosetta_translate_binaries_t
>(nullptr);
606 return reinterpret_cast<rosetta_translate_binaries_t
>(
607 dlsym(libRosetta
, "rosetta_translate_binaries"));
610 if (!rosetta_translate_binaries
) {
614 const char* pathPtr
= aBinaryPath
.get();
615 return rosetta_translate_binaries(&pathPtr
, 1);