1 /* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil -*-
3 * Copyright (c) 2014 Apple Inc. All rights reserved.
5 * @APPLE_LICENSE_HEADER_START@
7 * This file contains Original Code and/or Modifications of Original Code
8 * as defined in and that are subject to the Apple Public Source License
9 * Version 2.0 (the 'License'). You may not use this file except in
10 * compliance with the License. Please obtain a copy of the License at
11 * http://www.opensource.apple.com/apsl/ and read it before using this
14 * The Original Code and all software distributed under the License are
15 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
16 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
17 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
19 * Please see the License for the specific language governing rights and
20 * limitations under the License.
22 * @APPLE_LICENSE_HEADER_END@
25 #include <sys/types.h>
28 #include <mach/mach.h>
29 #include <mach/mach_time.h>
41 #include <sys/param.h>
42 #include <sys/sysctl.h>
43 #include <sys/resource.h>
57 #include "MachOProxy.h"
59 #include "mega-dylib-utils.h"
62 #import "MultiCacheBuilder.h"
64 #if !__has_feature(objc_arc)
65 #error The use of libdispatch in this files requires it to be compiled with ARC in order to avoid leaks
68 const std::string anchorsDirPath = "/private/var/db/dyld/shared_region_roots";
70 bool parsePathsFile( const std::string& filePath, std::set<std::string>& paths ) {
71 verboseLog( "parsing .paths file '%s'", filePath.c_str() );
72 std::ifstream myfile( filePath );
73 if ( myfile.is_open() ) {
75 while ( std::getline(myfile, line) ) {
76 size_t pos = line.find('#');
77 if ( pos != std::string::npos )
79 while ( line.size() != 0 && isspace(line.back()) ) {
82 if ( !line.empty() ) paths.insert( line );
91 static bool parseDirectoryOfPathsFiles(const std::string& dirPath, std::set<std::string>& paths)
93 DIR* dir = ::opendir(dirPath.c_str());
97 for (dirent* entry = ::readdir(dir); entry != NULL; entry = ::readdir(dir)) {
98 if ( entry->d_type == DT_REG || entry->d_type == DT_UNKNOWN ) {
99 // only look at regular files ending in .paths
100 if ( strcmp(&entry->d_name[entry->d_namlen-6], ".paths") == 0 ) {
102 std::string filePathStr = dirPath + "/" + entry->d_name;
103 const char* filePath = filePathStr.c_str();
104 if ( lstat(filePath, &statBuf) == -1 ) {
105 warning("can't access file '%s'", filePath);
107 else if ( S_ISREG(statBuf.st_mode) ) {
108 parsePathsFile(filePath, paths);
111 warning("not a regular file '%s'", filePath);
115 warning("ignoring file with wrong extension '%s'", entry->d_name);
123 static bool buildInitialPaths(const std::string& volumeRootPath, const std::string& overlayPath, std::set<std::string>& paths)
125 // in -root mode, look for roots in /rootpath/private/var/db/dyld/shared_region_roots
126 if ( volumeRootPath != "/" ) {
127 if ( parseDirectoryOfPathsFiles(volumeRootPath + "/" + anchorsDirPath, paths) )
129 // fallback to .paths files on boot volume
130 return parseDirectoryOfPathsFiles(anchorsDirPath, paths);
133 // in -overlay mode, look for .paths first in each /overlay/private/var/db/dyld/shared_region_roots
134 if ( !overlayPath.empty() ) {
135 parseDirectoryOfPathsFiles(overlayPath + "/" + anchorsDirPath, paths);
138 // look for .paths files in /private/var/db/dyld/shared_region_roots
139 return parseDirectoryOfPathsFiles(anchorsDirPath, paths);
142 bool fileExists(const std::string& path, bool& isSymLink)
145 if ( lstat(path.c_str(), &statBuf) == -1 )
147 isSymLink = S_ISLNK(statBuf.st_mode);
148 return S_ISREG(statBuf.st_mode) || isSymLink;
151 bool tryPath(const std::string& prefix, const std::string& path, std::string& foundPath, std::vector<std::string>& aliases)
153 foundPath = prefix + path;
155 if ( !fileExists(foundPath, isSymLink) )
158 // handle case where install name is a symlink to real file (e.g. libstdc++.6.dylib -> libstdc++.6.0.9.dylib)
159 char pathInSymLink[MAXPATHLEN];
160 long len = ::readlink(foundPath.c_str(), pathInSymLink, sizeof(pathInSymLink));
162 pathInSymLink[len] = '\0';
163 if ( pathInSymLink[0] != '/' ) {
164 std::string aliasPath = path;
165 size_t pos = aliasPath.rfind('/');
166 if ( pos != std::string::npos ) {
167 std::string newPath = aliasPath.substr(0,pos+1) + pathInSymLink;
168 aliases.push_back(newPath);
173 char realPath[MAXPATHLEN];
174 if ( ::realpath(foundPath.c_str(), realPath) ) {
175 if ( foundPath != realPath ) {
176 std::string altPath = realPath;
177 if ( !prefix.empty() ) {
178 if ( altPath.substr(0, prefix.size()) == prefix ) {
179 altPath = altPath.substr(prefix.size());
182 if ( altPath != path )
183 aliases.push_back(path);
185 aliases.push_back(altPath);
191 bool improvePath(const char* volumeRootPath, const std::vector<const char*>& overlayPaths,
192 const std::string& path, std::string& foundPath, std::vector<std::string>& aliases)
194 for (const char* overlay : overlayPaths) {
195 if ( tryPath(overlay, path, foundPath, aliases) )
198 if ( volumeRootPath[0] != '\0' ) {
199 if ( tryPath(volumeRootPath, path, foundPath, aliases) )
202 return tryPath("", path, foundPath, aliases);
205 std::string fileExists( const std::string& path ) {
206 const uint8_t* p = (uint8_t*)( -1 );
207 struct stat stat_buf;
210 std::tie( p, stat_buf, rootless ) = fileCache.cacheLoad( path );
211 if ( p != (uint8_t*)( -1 ) ) {
212 return normalize_absolute_file_path( path );
218 void populateManifest(Manifest& manifest, std::set<std::string> archs, const std::string& overlayPath,
219 const std::string& rootPath, const std::set<std::string>& paths) {
220 for ( const auto& arch : archs ) {
221 auto fallback = fallbackArchStringForArchString(arch);
222 std::set<std::string> allArchs = archs;
223 std::set<std::string> processedPaths;
224 std::set<std::string> unprocessedPaths = paths;
225 std::set<std::string> pathsToProcess;
226 std::set_difference( unprocessedPaths.begin(), unprocessedPaths.end(), processedPaths.begin(), processedPaths.end(),
227 std::inserter( pathsToProcess, pathsToProcess.begin() ) );
228 while ( !pathsToProcess.empty() ) {
229 for (const std::string path : pathsToProcess) {
230 processedPaths.insert(path);
231 std::string fullPath;
232 if ( rootPath != "/" ) {
233 // with -root, only look in the root path volume
234 fullPath = fileExists(rootPath + path);
237 // with -overlay, look first in overlay dir
238 if ( !overlayPath.empty() )
239 fullPath = fileExists(overlayPath + path);
240 // if not in overlay, look in boot volume
241 if ( fullPath.empty() )
242 fullPath = fileExists(path);
244 if ( fullPath.empty() )
246 auto proxies = MachOProxy::findDylibInfo(fullPath, true, true);
247 auto proxy = proxies.find(arch);
248 if (proxy == proxies.end())
249 proxy = proxies.find(fallback);
250 if (proxy == proxies.end())
253 for ( const auto& dependency : proxy->second->dependencies ) {
254 unprocessedPaths.insert( dependency );
257 if ( proxy->second->installName.empty() ) {
261 proxy->second->addAlias( path );
262 manifest.architectureFiles[arch].dylibs.insert(std::make_pair(proxy->second->installName,
263 Manifest::File(proxy->second)));
264 manifest.configurations["localhost"].architectures[arch].anchors.push_back( proxy->second->installName );
267 pathsToProcess.clear();
268 std::set_difference( unprocessedPaths.begin(), unprocessedPaths.end(), processedPaths.begin(), processedPaths.end(),
269 std::inserter( pathsToProcess, pathsToProcess.begin() ) );
274 static bool runningOnHaswell()
276 // check system is capable of running x86_64h code
277 struct host_basic_info info;
278 mach_msg_type_number_t count = HOST_BASIC_INFO_COUNT;
279 mach_port_t hostPort = mach_host_self();
280 kern_return_t result = host_info(hostPort, HOST_BASIC_INFO, (host_info_t)&info, &count);
281 mach_port_deallocate(mach_task_self(), hostPort);
283 return ( (result == KERN_SUCCESS) && (info.cpu_subtype == CPU_SUBTYPE_X86_64_H) );
287 #define TERMINATE_IF_LAST_ARG( s ) \
289 if ( i == argc - 1 ) terminate( s ); \
292 int main(int argc, const char* argv[])
294 std::string rootPath;
295 std::string overlayPath;
296 std::string platform = "osx";
297 std::string dylibListFile;
298 bool universal = false;
300 std::string cacheDir;
301 std::set<std::string> archStrs;
302 std::vector<Manifest::Anchor> anchors;
304 // parse command line options
305 for (int i = 1; i < argc; ++i) {
306 const char* arg = argv[i];
308 if (strcmp(arg, "-debug") == 0) {
310 } else if (strcmp(arg, "-verbose") == 0) {
312 } else if (strcmp(arg, "-dont_map_local_symbols") == 0) {
313 //We are going to ignore this
314 } else if (strcmp(arg, "-iPhone") == 0) {
315 platform = "iphoneos";
316 } else if (strcmp(arg, "-dylib_list") == 0) {
317 TERMINATE_IF_LAST_ARG("-dylib_list missing argument");
318 dylibListFile = argv[++i];
319 } else if ((strcmp(arg, "-root") == 0) || (strcmp(arg, "--root") == 0)) {
320 TERMINATE_IF_LAST_ARG("-root missing path argument\n");
321 rootPath = argv[++i];
322 } else if (strcmp(arg, "-overlay") == 0) {
323 TERMINATE_IF_LAST_ARG("-overlay missing path argument\n");
324 overlayPath = argv[++i];
325 } else if (strcmp(arg, "-cache_dir") == 0) {
326 TERMINATE_IF_LAST_ARG("-cache_dir missing path argument\n");
327 cacheDir = argv[++i];
328 } else if (strcmp(arg, "-arch") == 0) {
329 TERMINATE_IF_LAST_ARG("-arch missing argument\n");
330 archStrs.insert(argv[++i]);
331 } else if (strcmp(arg, "-force") == 0) {
333 } else if (strcmp(arg, "-sort_by_name") == 0) {
334 //No-op, we always do this now
335 } else if (strcmp(arg, "-universal_boot") == 0) {
339 terminate("unknown option: %s\n", arg);
343 terminate("unknown option: %s\n", arg);
347 setReturnNonZeroOnTerminate();
348 setWarnAnErrorPrefixes("update_dyld_shared_cache: warning: ", "update_dyld_shared_cache failed: ");
350 if ( !rootPath.empty() & !overlayPath.empty() )
351 terminate("-root and -overlay cannot be used together\n");
353 //FIXME realpath on root and overlays
355 if (rootPath.empty()) {
359 if ( cacheDir.empty() ) {
360 // write cache file into -root or -overlay directory, if used
361 if ( rootPath != "/" )
362 cacheDir = rootPath + MACOSX_DYLD_SHARED_CACHE_DIR;
363 else if ( !overlayPath.empty() )
364 cacheDir = overlayPath + MACOSX_DYLD_SHARED_CACHE_DIR;
366 cacheDir = MACOSX_DYLD_SHARED_CACHE_DIR;
370 if ( platform == "iphoneos" ) {
371 terminate("-iPhoneOS and -universal are incompatible\n");
376 if (archStrs.size() == 0) {
377 if ( platform == "iphoneos" ) {
378 terminate("Must specify -arch(s) when using -iPhone\n");
382 // <rdar://problem/26182089> -universal_boot should make all possible dyld caches
383 archStrs.insert("i386");
384 archStrs.insert("x86_64");
385 archStrs.insert("x86_64h");
388 // just make caches for this machine
389 archStrs.insert("i386");
390 archStrs.insert(runningOnHaswell() ? "x86_64h" : "x86_64");
395 int err = mkpath_np(cacheDir.c_str(), S_IRWXU | S_IRGRP|S_IXGRP | S_IROTH|S_IXOTH);
396 if (err != 0 && err != EEXIST) {
397 terminate("mkpath_np fail: %d", err);
402 std::set<std::string> paths;
404 if ( !dylibListFile.empty() ) {
405 if ( !parsePathsFile( dylibListFile, paths ) ) {
406 terminate( "could not build intiial paths\n" );
408 } else if ( !buildInitialPaths( rootPath, overlayPath, paths ) ) {
409 terminate( "could not build intiial paths\n" );
412 manifest.platform = platform;
413 populateManifest( manifest, archStrs, overlayPath, rootPath, paths );
415 // If the path we are writing to is trusted then our sources need to be trusted
416 // <rdar://problem/21166835> Can't update the update_dyld_shared_cache on a non-boot volume
417 bool requireDylibsBeRootlessProtected = isProtectedBySIP(cacheDir);
418 manifest.calculateClosure( requireDylibsBeRootlessProtected );
419 manifest.pruneClosure();
421 for (const std::string& archStr : archStrs) {
422 std::string cachePath = cacheDir + "/dyld_shared_cache_" + archStr;
423 if ( manifest.sameContentsAsCacheAtPath("localhost", archStr, cachePath) && !force ) {
424 manifest.configurations["localhost"].architectures.erase(archStr);
425 verboseLog("%s is already up to date", cachePath.c_str());
429 // If caches already up to date, do nothing
430 if ( manifest.configurations["localhost"].architectures.empty() )
431 dumpLogAndExit(false);
434 std::shared_ptr<MultiCacheBuilder> builder = std::make_shared<MultiCacheBuilder>(manifest, false, false, false, false, requireDylibsBeRootlessProtected);
435 builder->buildCaches(cacheDir);
437 // Save off spintrace data
438 std::string nuggetRoot = (overlayPath.empty() ? rootPath : overlayPath);
439 (void)dscsym_save_nuggets_for_current_caches(nuggetRoot.c_str());
441 // Now that all the build commands have been issued lets put a barrier in after then which can tear down the app after
442 // everything is written.
444 dumpLogAndExit(false);