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 "mega-dylib-utils.h"
58 #include "MultiCacheBuilder.h"
59 #include "MachOProxy.h"
63 #if !__has_feature(objc_arc)
64 #error The use of libdispatch in this files requires it to be compiled with ARC in order to avoid leaks
67 const std::string anchorsDirPath = "/private/var/db/dyld/shared_region_roots";
69 bool parsePathsFile( const std::string& filePath, std::set<std::string>& paths ) {
70 verboseLog( "parsing .paths file '%s'", filePath.c_str() );
71 std::ifstream myfile( filePath );
72 if ( myfile.is_open() ) {
74 while ( std::getline(myfile, line) ) {
75 size_t pos = line.find('#');
76 if ( pos != std::string::npos )
78 while ( line.size() != 0 && isspace(line.back()) ) {
81 if ( !line.empty() ) paths.insert( line );
90 static bool parseDirectoryOfPathsFiles(const std::string& dirPath, std::set<std::string>& paths)
92 DIR* dir = ::opendir(dirPath.c_str());
96 for (dirent* entry = ::readdir(dir); entry != NULL; entry = ::readdir(dir)) {
97 if ( entry->d_type == DT_REG || entry->d_type == DT_UNKNOWN ) {
98 // only look at regular files ending in .paths
99 if ( strcmp(&entry->d_name[entry->d_namlen-6], ".paths") == 0 ) {
101 std::string filePathStr = dirPath + "/" + entry->d_name;
102 const char* filePath = filePathStr.c_str();
103 if ( lstat(filePath, &statBuf) == -1 ) {
104 warning("can't access file '%s'", filePath);
106 else if ( S_ISREG(statBuf.st_mode) ) {
107 parsePathsFile(filePath, paths);
110 warning("not a regular file '%s'", filePath);
114 warning("ignoring file with wrong extension '%s'", entry->d_name);
122 static bool buildInitialPaths(const std::string& volumeRootPath, const std::string& overlayPath, std::set<std::string>& paths)
124 // in -root mode, look for roots in /rootpath/private/var/db/dyld/shared_region_roots
125 if ( volumeRootPath != "/" ) {
126 if ( parseDirectoryOfPathsFiles(volumeRootPath + "/" + anchorsDirPath, paths) )
128 // fallback to .paths files on boot volume
129 return parseDirectoryOfPathsFiles(anchorsDirPath, paths);
132 // in -overlay mode, look for .paths first in each /overlay/private/var/db/dyld/shared_region_roots
133 if ( !overlayPath.empty() ) {
134 parseDirectoryOfPathsFiles(overlayPath + "/" + anchorsDirPath, paths);
137 // look for .paths files in /private/var/db/dyld/shared_region_roots
138 return parseDirectoryOfPathsFiles(anchorsDirPath, paths);
141 bool fileExists(const std::string& path, bool& isSymLink)
144 if ( lstat(path.c_str(), &statBuf) == -1 )
146 isSymLink = S_ISLNK(statBuf.st_mode);
147 return S_ISREG(statBuf.st_mode) || isSymLink;
150 bool tryPath(const std::string& prefix, const std::string& path, std::string& foundPath, std::vector<std::string>& aliases)
152 foundPath = prefix + path;
154 if ( !fileExists(foundPath, isSymLink) )
157 // handle case where install name is a symlink to real file (e.g. libstdc++.6.dylib -> libstdc++.6.0.9.dylib)
158 char pathInSymLink[MAXPATHLEN];
159 long len = ::readlink(foundPath.c_str(), pathInSymLink, sizeof(pathInSymLink));
161 pathInSymLink[len] = '\0';
162 if ( pathInSymLink[0] != '/' ) {
163 std::string aliasPath = path;
164 size_t pos = aliasPath.rfind('/');
165 if ( pos != std::string::npos ) {
166 std::string newPath = aliasPath.substr(0,pos+1) + pathInSymLink;
167 aliases.push_back(newPath);
172 char realPath[MAXPATHLEN];
173 if ( ::realpath(foundPath.c_str(), realPath) ) {
174 if ( foundPath != realPath ) {
175 std::string altPath = realPath;
176 if ( !prefix.empty() ) {
177 if ( altPath.substr(0, prefix.size()) == prefix ) {
178 altPath = altPath.substr(prefix.size());
181 if ( altPath != path )
182 aliases.push_back(path);
184 aliases.push_back(altPath);
190 bool improvePath(const char* volumeRootPath, const std::vector<const char*>& overlayPaths,
191 const std::string& path, std::string& foundPath, std::vector<std::string>& aliases)
193 for (const char* overlay : overlayPaths) {
194 if ( tryPath(overlay, path, foundPath, aliases) )
197 if ( volumeRootPath[0] != '\0' ) {
198 if ( tryPath(volumeRootPath, path, foundPath, aliases) )
201 return tryPath("", path, foundPath, aliases);
204 static bool runningOnHaswell()
206 // check system is capable of running x86_64h code
207 struct host_basic_info info;
208 mach_msg_type_number_t count = HOST_BASIC_INFO_COUNT;
209 mach_port_t hostPort = mach_host_self();
210 kern_return_t result = host_info(hostPort, HOST_BASIC_INFO, (host_info_t)&info, &count);
211 mach_port_deallocate(mach_task_self(), hostPort);
213 return ( (result == KERN_SUCCESS) && (info.cpu_subtype == CPU_SUBTYPE_X86_64_H) );
217 #define TERMINATE_IF_LAST_ARG( s ) \
219 if ( i == argc - 1 ) terminate( s ); \
222 int main(int argc, const char* argv[])
224 std::string rootPath;
225 std::string overlayPath;
226 std::string platform = "osx";
227 std::string dylibListFile;
228 bool universal = false;
230 std::string cacheDir;
231 std::set<std::string> archStrs;
232 std::vector<Manifest::Anchor> anchors;
234 // parse command line options
235 for (int i = 1; i < argc; ++i) {
236 const char* arg = argv[i];
238 if (strcmp(arg, "-debug") == 0) {
240 } else if (strcmp(arg, "-verbose") == 0) {
242 } else if (strcmp(arg, "-dont_map_local_symbols") == 0) {
243 //We are going to ignore this
244 } else if (strcmp(arg, "-iPhone") == 0) {
245 platform = "iphoneos";
246 } else if (strcmp(arg, "-dylib_list") == 0) {
247 TERMINATE_IF_LAST_ARG("-dylib_list missing argument");
248 dylibListFile = argv[++i];
249 } else if ((strcmp(arg, "-root") == 0) || (strcmp(arg, "--root") == 0)) {
250 TERMINATE_IF_LAST_ARG("-root missing path argument\n");
251 rootPath = argv[++i];
252 } else if (strcmp(arg, "-overlay") == 0) {
253 TERMINATE_IF_LAST_ARG("-overlay missing path argument\n");
254 overlayPath = argv[++i];
255 } else if (strcmp(arg, "-cache_dir") == 0) {
256 TERMINATE_IF_LAST_ARG("-cache_dir missing path argument\n");
257 cacheDir = argv[++i];
258 } else if (strcmp(arg, "-arch") == 0) {
259 TERMINATE_IF_LAST_ARG("-arch missing argument\n");
260 archStrs.insert(argv[++i]);
261 } else if (strcmp(arg, "-force") == 0) {
263 } else if (strcmp(arg, "-sort_by_name") == 0) {
264 //No-op, we always do this now
265 } else if (strcmp(arg, "-universal_boot") == 0) {
269 terminate("unknown option: %s\n", arg);
273 terminate("unknown option: %s\n", arg);
277 setReturnNonZeroOnTerminate();
278 setWarnAnErrorPrefixes("update_dyld_shared_cache: warning: ", "update_dyld_shared_cache failed: ");
280 if ( !rootPath.empty() & !overlayPath.empty() )
281 terminate("-root and -overlay cannot be used together\n");
283 //FIXME realpath on root and overlays
285 if (rootPath.empty()) {
289 if ( cacheDir.empty() ) {
290 // write cache file into -root or -overlay directory, if used
291 if ( rootPath != "/" )
292 cacheDir = rootPath + MACOSX_DYLD_SHARED_CACHE_DIR;
293 else if ( !overlayPath.empty() )
294 cacheDir = overlayPath + MACOSX_DYLD_SHARED_CACHE_DIR;
296 cacheDir = MACOSX_DYLD_SHARED_CACHE_DIR;
300 if ( platform == "iphoneos" ) {
301 terminate("-iPhoneOS and -universal are incompatible\n");
306 if (archStrs.size() == 0) {
307 if ( platform == "iphoneos" ) {
308 terminate("Must specify -arch(s) when using -iPhone\n");
312 // <rdar://problem/26182089> -universal_boot should make all possible dyld caches
313 archStrs.insert("i386");
314 archStrs.insert("x86_64");
315 archStrs.insert("x86_64h");
318 // just make caches for this machine
319 archStrs.insert("i386");
320 archStrs.insert(runningOnHaswell() ? "x86_64h" : "x86_64");
325 int err = mkpath_np(cacheDir.c_str(), S_IRWXU | S_IRGRP|S_IXGRP | S_IROTH|S_IXOTH);
326 if (err != 0 && err != EEXIST) {
327 terminate("mkpath_np fail: %d", err);
330 std::set<std::string> paths;
332 if ( !dylibListFile.empty() ) {
333 if ( !parsePathsFile( dylibListFile, paths ) ) {
334 terminate( "could not build intiial paths\n" );
336 } else if ( !buildInitialPaths( rootPath, overlayPath, paths ) ) {
337 terminate( "could not build intiial paths\n" );
340 Manifest manifest(archStrs, overlayPath, rootPath, paths);
342 manifest.setPlatform(platform);
344 // If the path we are writing to is trusted then our sources need to be trusted
345 // <rdar://problem/21166835> Can't update the update_dyld_shared_cache on a non-boot volume
346 bool requireDylibsBeRootlessProtected = isProtectedBySIP(cacheDir);
347 manifest.calculateClosure( requireDylibsBeRootlessProtected );
349 for (const std::string& archStr : archStrs) {
350 std::string cachePath = cacheDir + "/dyld_shared_cache_" + archStr;
351 if (!force && manifest.sameContentsAsCacheAtPath("localhost", archStr, cachePath)) {
352 manifest.remove("localhost", archStr);
353 verboseLog("%s is already up to date", cachePath.c_str());
357 if (manifest.empty()) {
358 dumpLogAndExit(false);
362 std::shared_ptr<MultiCacheBuilder> builder = std::make_shared<MultiCacheBuilder>(manifest, false, false, false, false, requireDylibsBeRootlessProtected);
363 builder->buildCaches(cacheDir);
365 // Save off spintrace data
366 std::string nuggetRoot = (overlayPath.empty() ? rootPath : overlayPath);
367 (void)dscsym_save_nuggets_for_current_caches(nuggetRoot.c_str());
369 // Now that all the build commands have been issued lets put a barrier in after then which can tear down the app after
370 // everything is written.
372 dumpLogAndExit(false);