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/. */
6 
7 #include "nsMacUtilsImpl.h"
8 
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"
21 
22 #if defined(MOZ_SANDBOX)
23 #  include "mozilla/SandboxSettings.h"
24 #endif
25 
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>
32 
33 NS_IMPL_ISUPPORTS(nsMacUtilsImpl, nsIMacUtils)
34 
35 using mozilla::StaticMutexAutoLock;
36 using mozilla::Unused;
37 
38 #if defined(MOZ_SANDBOX)
39 StaticAutoPtr<nsCString> nsMacUtilsImpl::sCachedAppPath;
40 StaticMutex nsMacUtilsImpl::sCachedAppPathMutex;
41 #endif
42 
43 std::atomic<uint32_t> nsMacUtilsImpl::sBundleArchMaskAtomic = 0;
44 
45 #if defined(__aarch64__)
46 std::atomic<bool> nsMacUtilsImpl::sIsXULTranslated = false;
47 #endif
48 
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"
53 
54 // Workaround this constant not being available in the macOS SDK
55 #define kCFBundleExecutableArchitectureARM64 0x0100000c
56 
57 // Initialize with Unknown until we've checked if TCSM is available to set
58 Atomic<nsMacUtilsImpl::TCSMStatus> nsMacUtilsImpl::sTCSMStatus(TCSM_Unknown);
59 
GetArchString(nsAString & aArchString)60 nsresult nsMacUtilsImpl::GetArchString(nsAString& aArchString) {
61   if (!mBinaryArchs.IsEmpty()) {
62     aArchString.Assign(mBinaryArchs);
63     return NS_OK;
64   }
65 
66   uint32_t archMask = base::PROCESS_ARCH_INVALID;
67   nsresult rv = GetArchitecturesForBundle(&archMask);
68   NS_ENSURE_SUCCESS(rv, rv);
69 
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");
74   }
75 
76   if (archMask & base::PROCESS_ARCH_I386) {
77     if (!mBinaryArchs.IsEmpty()) {
78       mBinaryArchs.Append('-');
79     }
80     mBinaryArchs.AppendLiteral("i386");
81   }
82 
83   if (archMask & base::PROCESS_ARCH_PPC_64) {
84     if (!mBinaryArchs.IsEmpty()) {
85       mBinaryArchs.Append('-');
86     }
87     mBinaryArchs.AppendLiteral("ppc64");
88   }
89 
90   if (archMask & base::PROCESS_ARCH_X86_64) {
91     if (!mBinaryArchs.IsEmpty()) {
92       mBinaryArchs.Append('-');
93     }
94     mBinaryArchs.AppendLiteral("x86_64");
95   }
96 
97   if (archMask & base::PROCESS_ARCH_ARM_64) {
98     if (!mBinaryArchs.IsEmpty()) {
99       mBinaryArchs.Append('-');
100     }
101     mBinaryArchs.AppendLiteral("arm64");
102   }
103 
104   aArchString.Truncate();
105   aArchString.Assign(mBinaryArchs);
106 
107   return (aArchString.IsEmpty() ? NS_ERROR_FAILURE : NS_OK);
108 }
109 
110 NS_IMETHODIMP
GetArchitecturesInBinary(nsAString & aArchString)111 nsMacUtilsImpl::GetArchitecturesInBinary(nsAString& aArchString) {
112   return GetArchString(aArchString);
113 }
114 
115 // True when running under binary translation (Rosetta).
116 NS_IMETHODIMP
GetIsTranslated(bool * aIsTranslated)117 nsMacUtilsImpl::GetIsTranslated(bool* aIsTranslated) {
118 #ifdef __ppc__
119   static bool sInitialized = false;
120 
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;
125 
126   if (!sInitialized) {
127     size_t sz = sizeof(sIsNative);
128     sysctlbyname("sysctl.proc_native", &sIsNative, &sz, nullptr, 0);
129     sInitialized = true;
130   }
131 
132   *aIsTranslated = !sIsNative;
133 #else
134   // Translation only exists for ppc code.  Other architectures aren't
135   // translated.
136   *aIsTranslated = false;
137 #endif
138 
139   return NS_OK;
140 }
141 
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.
GetAppPath(nsCString & aAppPath)148 bool nsMacUtilsImpl::GetAppPath(nsCString& aAppPath) {
149   StaticMutexAutoLock lock(sCachedAppPathMutex);
150   if (sCachedAppPath) {
151     aAppPath.Assign(*sCachedAppPath);
152     return true;
153   }
154 
155   nsAutoCString appPath;
156   nsAutoCString appBinaryPath(
157       (CommandLine::ForCurrentProcess()->argv()[0]).c_str());
158 
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);
169 
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;
179       }
180     }
181 
182     appPath.Assign(Substring(start, end));
183   } else {
184     return false;
185   }
186 
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;
192   }
193 
194   rv = app->Normalize();
195   if (NS_FAILED(rv)) {
196     return false;
197   }
198   app->GetNativePath(aAppPath);
199 
200   if (!sCachedAppPath) {
201     sCachedAppPath = new nsCString(aAppPath);
202 
203     if (NS_IsMainThread()) {
204       nsMacUtilsImpl::ClearCachedAppPathOnShutdown();
205     } else {
206       NS_DispatchToMainThread(NS_NewRunnableFunction(
207           "nsMacUtilsImpl::ClearCachedAppPathOnShutdown",
208           [] { nsMacUtilsImpl::ClearCachedAppPathOnShutdown(); }));
209     }
210   }
211 
212   return true;
213 }
214 
ClearCachedAppPathOnShutdown()215 nsresult nsMacUtilsImpl::ClearCachedAppPathOnShutdown() {
216   MOZ_ASSERT(NS_IsMainThread());
217   ClearOnShutdown(&sCachedAppPath);
218   return NS_OK;
219 }
220 
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.)
GetBloatLogDir(nsCString & aDirectoryPath)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");
229   }
230   if (!bloatLog.IsEmpty() && bloatLog != "1" && bloatLog != "2") {
231     return GetDirectoryPath(bloatLog.get(), aDirectoryPath);
232   }
233   return NS_OK;
234 }
235 
236 // Given a path to a file, return the directory which contains it.
GetDirectoryPath(const char * aPath,nsCString & aDirectoryPath)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);
242 
243   rv = file->InitWithNativePath(nsDependentCString(aPath));
244   NS_ENSURE_SUCCESS(rv, rv);
245 
246   nsCOMPtr<nsIFile> directoryFile;
247   rv = file->GetParent(getter_AddRefs(directoryFile));
248   NS_ENSURE_SUCCESS(rv, rv);
249 
250   rv = directoryFile->Normalize();
251   NS_ENSURE_SUCCESS(rv, rv);
252 
253   if (NS_FAILED(directoryFile->GetNativePath(aDirectoryPath))) {
254     MOZ_CRASH("Failed to get path for an nsIFile");
255   }
256   return NS_OK;
257 }
258 #  endif /* DEBUG */
259 #endif   /* MOZ_SANDBOX */
260 
261 /* static */
IsTCSMAvailable()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;
272     }
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);
279   }
280   return (sTCSMStatus == TCSM_Available);
281 }
282 
283 /* static */
EnableTCSM()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;
289   }
290   return NS_OK;
291 }
292 
293 /*
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.
297  */
298 /* static */
EnableTCSMIfAvailable()299 void nsMacUtilsImpl::EnableTCSMIfAvailable() {
300   if (IsTCSMAvailable()) {
301     if (NS_FAILED(EnableTCSM())) {
302       NS_WARNING("Failed to enable TCSM");
303     }
304     MOZ_ASSERT(IsTCSMEnabled());
305   }
306 }
307 
308 #if defined(DEBUG)
309 /* static */
IsTCSMEnabled()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);
315 }
316 #endif
317 
318 // Returns 0 on error.
319 /* static */
GetPhysicalCPUCount()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;
326   }
327   return oldVal;
328 }
329 
330 /*
331  * Helper function to read a string value for a given key from the .app's
332  * Info.plist.
333  */
GetStringValueFromBundlePlist(const nsAString & aKey,nsAutoCString & aValue)334 static nsresult GetStringValueFromBundlePlist(const nsAString& aKey,
335                                               nsAutoCString& aValue) {
336   CFBundleRef mainBundle = CFBundleGetMainBundle();
337   if (mainBundle == nullptr) {
338     return NS_ERROR_FAILURE;
339   }
340 
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;
345   }
346 
347   nsAutoCString keyAutoCString = NS_ConvertUTF16toUTF8(aKey);
348   CFStringRef key = CFStringCreateWithCString(
349       kCFAllocatorDefault, keyAutoCString.get(), kCFStringEncodingUTF8);
350   if (key == nullptr) {
351     return NS_ERROR_FAILURE;
352   }
353 
354   CFStringRef value = (CFStringRef)CFDictionaryGetValue(bundleInfoDict, key);
355   CFRelease(key);
356   if (value == nullptr) {
357     return NS_ERROR_FAILURE;
358   }
359 
360   CFIndex valueLength = CFStringGetLength(value);
361   if (valueLength == 0) {
362     return NS_ERROR_FAILURE;
363   }
364 
365   const char* valueCString =
366       CFStringGetCStringPtr(value, kCFStringEncodingUTF8);
367   if (valueCString) {
368     aValue.Assign(valueCString);
369     return NS_OK;
370   }
371 
372   CFIndex maxLength =
373       CFStringGetMaximumSizeForEncoding(valueLength, kCFStringEncodingUTF8) + 1;
374   char* valueBuffer = static_cast<char*>(moz_xmalloc(maxLength));
375 
376   if (!CFStringGetCString(value, valueBuffer, maxLength,
377                           kCFStringEncodingUTF8)) {
378     free(valueBuffer);
379     return NS_ERROR_FAILURE;
380   }
381 
382   aValue.Assign(valueBuffer);
383   free(valueBuffer);
384   return NS_OK;
385 }
386 
387 /*
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.
390  */
GetDirFromBundlePlist(const nsAString & aKey,nsIFile ** aDir)391 static nsresult GetDirFromBundlePlist(const nsAString& aKey, nsIFile** aDir) {
392   nsresult rv;
393 
394   nsAutoCString dirPath;
395   rv = GetStringValueFromBundlePlist(aKey, dirPath);
396   NS_ENSURE_SUCCESS(rv, rv);
397 
398   nsCOMPtr<nsIFile> dir;
399   rv = NS_NewLocalFile(NS_ConvertUTF8toUTF16(dirPath), false,
400                        getter_AddRefs(dir));
401   NS_ENSURE_SUCCESS(rv, rv);
402 
403   rv = dir->Normalize();
404   NS_ENSURE_SUCCESS(rv, rv);
405 
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;
411   }
412 
413   dir.swap(*aDir);
414   return NS_OK;
415 }
416 
GetRepoDir(nsIFile ** aRepoDir)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);
423 }
424 
GetObjDir(nsIFile ** aObjDir)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);
431 }
432 
433 /* static */
GetArchitecturesForBundle(uint32_t * aArchMask)434 nsresult nsMacUtilsImpl::GetArchitecturesForBundle(uint32_t* aArchMask) {
435   MOZ_ASSERT(aArchMask);
436 
437   *aArchMask = sBundleArchMaskAtomic;
438   if (*aArchMask != 0) {
439     return NS_OK;
440   }
441 
442   CFBundleRef mainBundle = ::CFBundleGetMainBundle();
443   if (!mainBundle) {
444     return NS_ERROR_FAILURE;
445   }
446 
447   CFArrayRef archList = ::CFBundleCopyExecutableArchitectures(mainBundle);
448   if (!archList) {
449     return NS_ERROR_FAILURE;
450   }
451 
452   CFIndex archCount = ::CFArrayGetCount(archList);
453   for (CFIndex i = 0; i < archCount; i++) {
454     CFNumberRef arch =
455         static_cast<CFNumberRef>(::CFArrayGetValueAtIndex(archList, i));
456 
457     int archInt = 0;
458     if (!::CFNumberGetValue(arch, kCFNumberIntType, &archInt)) {
459       ::CFRelease(archList);
460       return NS_ERROR_FAILURE;
461     }
462 
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;
473     }
474   }
475 
476   ::CFRelease(archList);
477 
478   sBundleArchMaskAtomic = *aArchMask;
479 
480   return NS_OK;
481 }
482 
483 /* static */
GetArchitecturesForBinary(const char * aPath,uint32_t * aArchMask)484 nsresult nsMacUtilsImpl::GetArchitecturesForBinary(const char* aPath,
485                                                    uint32_t* aArchMask) {
486   MOZ_ASSERT(aArchMask);
487 
488   *aArchMask = 0;
489 
490   CFURLRef url = ::CFURLCreateFromFileSystemRepresentation(
491       kCFAllocatorDefault, (const UInt8*)aPath, strlen(aPath), false);
492   if (!url) {
493     return NS_ERROR_FAILURE;
494   }
495 
496   CFArrayRef archs = ::CFBundleCopyExecutableArchitecturesForURL(url);
497   if (!archs) {
498     CFRelease(url);
499     return NS_ERROR_FAILURE;
500   }
501 
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;
509     }
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;
519     }
520   }
521 
522   CFRelease(url);
523   CFRelease(archs);
524 
525   // We expect x86 or ARM64 or both.
526   if (*aArchMask == 0) {
527     return NS_ERROR_UNEXPECTED;
528   }
529 
530   return NS_OK;
531 }
532 
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 */
PreTranslateXUL()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;
544   }
545 
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;
551   }
552   xulPath.Append("/Contents/MacOS/XUL");
553 
554   return PreTranslateBinary(xulPath);
555 }
556 
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 */
PreTranslateBinary(nsCString aBinaryPath)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;
571   }
572 
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;
578   }
579 
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;
589   }
590 #  pragma clang diagnostic pop
591 
592   if (aBinaryPath.IsEmpty()) {
593     return -1;
594   }
595 
596   // int rosetta_translate_binaries(const char*[] paths, int npaths)
597   using rosetta_translate_binaries_t = int (*)(const char*[], int);
598 
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);
604     }
605 
606     return reinterpret_cast<rosetta_translate_binaries_t>(
607         dlsym(libRosetta, "rosetta_translate_binaries"));
608   }();
609 
610   if (!rosetta_translate_binaries) {
611     return -1;
612   }
613 
614   const char* pathPtr = aBinaryPath.get();
615   return rosetta_translate_binaries(&pathPtr, 1);
616 }
617 
618 #endif
619