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"
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 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
);
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;
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
);
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
);
84 aAppPath
.Assign(*sCachedAppPath
);
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
)) {
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
)) {
109 appBinaryPath
.BeginReading(start
);
115 appPath
.Assign(Substring(start
, end
));
120 nsCOMPtr
<nsIFile
> app
;
121 nsresult rv
= NS_NewLocalFile(NS_ConvertUTF8toUTF16(appPath
), true,
122 getter_AddRefs(app
));
127 rv
= app
->Normalize();
131 app
->GetNativePath(aAppPath
);
133 if (!sCachedAppPath
) {
134 sCachedAppPath
= new nsCString(aAppPath
);
136 if (NS_IsMainThread()) {
137 ClearCachedAppPathOnShutdown();
139 NS_DispatchToMainThread(
140 NS_NewRunnableFunction("ClearCachedAppPathOnShutdown",
141 [] { ClearCachedAppPathOnShutdown(); }));
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
);
165 // Given a path to a file, return the directory which contains it.
166 nsresult
nsMacUtilsImpl::GetDirectoryPath(const char* aPath
,
167 nsCString
& aDirectoryPath
) {
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");
187 #endif /* MOZ_SANDBOX && DEBUG */
190 bool nsMacUtilsImpl::IsTCSMAvailable() {
191 if (sTCSMStatus
== TCSM_Unknown
) {
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
;
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() {
213 int rv
= sysctlbyname("kern.tcsm_enable", NULL
, 0, &newVal
, sizeof(newVal
));
215 return NS_ERROR_UNEXPECTED
;
221 static bool IsTCSMEnabled() {
223 size_t oldValSize
= sizeof(oldVal
);
224 int rv
= sysctlbyname("kern.tcsm_enable", &oldVal
, &oldValSize
, NULL
, 0);
225 return (rv
== 0) && (oldVal
!= 0);
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.
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.
246 uint32_t nsMacUtilsImpl::GetPhysicalCPUCount() {
248 size_t oldValSize
= sizeof(oldVal
);
249 int rv
= sysctlbyname("hw.physicalcpu_max", &oldVal
, &oldValSize
, NULL
, 0);
257 * Helper function to read a string value for a given key from the .app's
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
);
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
);
294 aValue
.Assign(valueCString
);
299 CFStringGetMaximumSizeForEncoding(valueLength
, kCFStringEncodingUTF8
) + 1;
300 char* valueBuffer
= static_cast<char*>(moz_xmalloc(maxLength
));
302 if (!CFStringGetCString(value
, valueBuffer
, maxLength
,
303 kCFStringEncodingUTF8
)) {
305 return NS_ERROR_FAILURE
;
308 aValue
.Assign(valueBuffer
);
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
) {
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
);
336 return NS_ERROR_FILE_NOT_DIRECTORY
;
343 nsresult
nsMacUtilsImpl::GetRepoDir(nsIFile
** aRepoDir
) {
344 #if defined(MOZ_SANDBOX)
345 MOZ_ASSERT(!mozilla::IsPackagedBuild());
347 return GetDirFromBundlePlist(NS_LITERAL_STRING_FROM_CSTRING(MAC_DEV_REPO_KEY
),
351 nsresult
nsMacUtilsImpl::GetObjDir(nsIFile
** aObjDir
) {
352 #if defined(MOZ_SANDBOX)
353 MOZ_ASSERT(!mozilla::IsPackagedBuild());
355 return GetDirFromBundlePlist(NS_LITERAL_STRING_FROM_CSTRING(MAC_DEV_OBJ_KEY
),
360 nsresult
nsMacUtilsImpl::GetArchitecturesForBundle(uint32_t* aArchMask
) {
361 MOZ_ASSERT(aArchMask
);
363 *aArchMask
= sBundleArchMaskAtomic
;
364 if (*aArchMask
!= 0) {
368 CFBundleRef mainBundle
= ::CFBundleGetMainBundle();
370 return NS_ERROR_FAILURE
;
373 CFArrayRef archList
= ::CFBundleCopyExecutableArchitectures(mainBundle
);
375 return NS_ERROR_FAILURE
;
378 CFIndex archCount
= ::CFArrayGetCount(archList
);
379 for (CFIndex i
= 0; i
< archCount
; i
++) {
381 static_cast<CFNumberRef
>(::CFArrayGetValueAtIndex(archList
, i
));
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
;
410 nsresult
nsMacUtilsImpl::GetArchitecturesForBinary(const char* aPath
,
411 uint32_t* aArchMask
) {
412 MOZ_ASSERT(aArchMask
);
416 CFURLRef url
= ::CFURLCreateFromFileSystemRepresentation(
417 kCFAllocatorDefault
, (const UInt8
*)aPath
, strlen(aPath
), false);
419 return NS_ERROR_FAILURE
;
422 CFArrayRef archs
= ::CFBundleCopyExecutableArchitecturesForURL(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
, ¤tArchInt
)) {
436 switch (currentArchInt
) {
437 case kCFBundleExecutableArchitectureX86_64
:
438 *aArchMask
|= base::PROCESS_ARCH_X86_64
;
440 case kCFBundleExecutableArchitectureARM64
:
441 *aArchMask
|= base::PROCESS_ARCH_ARM_64
;
451 // We expect x86 or ARM64 or both.
452 if (*aArchMask
== 0) {
453 return NS_ERROR_UNEXPECTED
;
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.
465 int nsMacUtilsImpl::PreTranslateXUL() {
466 bool expected
= false;
467 if (!sIsXULTranslated
.compare_exchange_strong(expected
, true)) {
468 // Translation is already done or in progress.
472 // Get the path to XUL by first getting the
473 // outer .app path and appending the path to XUL.
475 if (!GetAppPath(xulPath
)) {
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
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()) {
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()) {
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
)) {
516 # pragma clang diagnostic pop
518 if (aBinaryPath
.IsEmpty()) {
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
= []() {
527 dlopen("/usr/lib/libRosetta.dylib", RTLD_LAZY
| RTLD_LOCAL
);
529 return static_cast<rosetta_translate_binaries_t
>(nullptr);
532 return reinterpret_cast<rosetta_translate_binaries_t
>(
533 dlsym(libRosetta
, "rosetta_translate_binaries"));
536 if (!rosetta_translate_binaries
) {
540 const char* pathPtr
= aBinaryPath
.get();
541 return rosetta_translate_binaries(&pathPtr
, 1);